diff --git a/.dockerignore b/.dockerignore index e9401f0cc9e..0e3b22687d0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,3 @@ docs +!docs/coverage charts diff --git a/.github/issue_label_bot.yaml b/.github/issue_label_bot.yaml new file mode 100644 index 00000000000..de9464b29de --- /dev/null +++ b/.github/issue_label_bot.yaml @@ -0,0 +1,5 @@ +label-alias: + bug: 'kind/bug' + feature_request: 'kind/feature' + feature: 'kind/feature' + question: 'kind/question' diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000000..7a78437b5d4 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,31 @@ + + +**What this PR does / why we need it**: + +**Which issue(s) this PR fixes**: + +Fixes # + +**Does this PR introduce a user-facing change?**: + +```release-note + +``` diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 00000000000..4e8f2b1d889 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,18 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 60 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 +# Issues with these labels will never be considered stale +exemptLabels: + - pinned + - security + - keep-open +# Label to use when marking an issue as stale +staleLabel: wontfix +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false diff --git a/.github/workflows/code_standards.yaml b/.github/workflows/code_standards.yaml new file mode 100644 index 00000000000..2077d751824 --- /dev/null +++ b/.github/workflows/code_standards.yaml @@ -0,0 +1,32 @@ +name: code standards + +on: [push, pull_request] + +jobs: + lint-java: + container: gcr.io/kf-feast/feast-ci:latest + runs-on: [ubuntu-latest] + steps: + - uses: actions/checkout@v2 + - name: lint java + run: make lint-java + + lint-python: + container: gcr.io/kf-feast/feast-ci:latest + runs-on: [ubuntu-latest] + steps: + - uses: actions/checkout@v2 + - name: install dependencies + run: make install-python-ci-dependencies + - name: lint python + run: make lint-python + + lint-go: + container: gcr.io/kf-feast/feast-ci:latest + runs-on: [ubuntu-latest] + steps: + - uses: actions/checkout@v2 + - name: install dependencies + run: make install-go-ci-dependencies + - name: lint go + run: make lint-go diff --git a/.github/workflows/docker_compose_tests.yml b/.github/workflows/docker_compose_tests.yml new file mode 100644 index 00000000000..28411b88802 --- /dev/null +++ b/.github/workflows/docker_compose_tests.yml @@ -0,0 +1,12 @@ +name: docker compose tests + +on: [push, pull_request] + +jobs: + basic-redis-e2e-tests-docker-compose: + runs-on: ubuntu-latest + name: basic redis e2e tests on docker compose + steps: + - uses: actions/checkout@v2 + - name: test docker compose + run: ./infra/scripts/test-docker-compose.sh \ No newline at end of file diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml new file mode 100644 index 00000000000..f71788a90bb --- /dev/null +++ b/.github/workflows/unit_tests.yml @@ -0,0 +1,45 @@ +name: unit tests + +on: [push, pull_request] + +jobs: + unit-test-java: + runs-on: ubuntu-latest + container: gcr.io/kf-feast/feast-ci:latest + name: unit test java + steps: + - uses: actions/checkout@v2 + - uses: actions/cache@v1 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: test java + run: make test-java-with-coverage + - uses: actions/upload-artifact@v2 + with: + name: java-coverage-report + path: ${{ github.workspace }}/docs/coverage/java/target/site/jacoco-aggregate/ + + unit-test-python: + runs-on: ubuntu-latest + container: gcr.io/kf-feast/feast-ci:latest + name: unit test python + steps: + - uses: actions/checkout@v2 + - name: install python + run: make install-python + - name: test python + run: make test-python + + unit-test-go: + runs-on: ubuntu-latest + container: gcr.io/kf-feast/feast-ci:latest + name: unit test go + steps: + - uses: actions/checkout@v2 + - name: install dependencies + run: make compile-protos-go + - name: test go + run: make test-go diff --git a/.gitignore b/.gitignore index a8c5e3fe0ba..b2c3f77f8c3 100644 --- a/.gitignore +++ b/.gitignore @@ -179,3 +179,8 @@ dmypy.json .flattened-pom.xml sdk/python/docs/html + +# Generated python code +*_pb2.py +*_pb2.pyi +*_pb2_grpc.py diff --git a/.helmdocsignore b/.helmdocsignore new file mode 100644 index 00000000000..8f246bffece --- /dev/null +++ b/.helmdocsignore @@ -0,0 +1,6 @@ +infra/charts/feast/charts/postgresql +infra/charts/feast/charts/kafka +infra/charts/feast/charts/redis +infra/charts/feast/charts/prometheus-statsd-exporter +infra/charts/feast/charts/prometheus +infra/charts/feast/charts/grafana diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000000..f7cf514e580 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,8 @@ +repos: + - repo: local + hooks: + - id: lint + name: Lint + stages: [commit] + language: system + entry: make lint \ No newline at end of file diff --git a/.prow/config.yaml b/.prow/config.yaml index c63d3dce797..9011ef801b4 100644 --- a/.prow/config.yaml +++ b/.prow/config.yaml @@ -41,7 +41,7 @@ deck: tide: queries: - repos: - - gojek/feast + - feast-dev/feast labels: - lgtm - approved @@ -51,8 +51,9 @@ tide: - do-not-merge/invalid-owners-file - do-not-merge/work-in-progress - needs-rebase + - needs-kind merge_method: - gojek/feast: squash + feast-dev/feast: squash blocker_label: merge-blocker squash_label: tide/squash @@ -60,66 +61,127 @@ tide: # https://github.com/kubernetes/test-infra/blob/6571843b1aa7bd6cf577a7a8b9e9971241f424d5/prow/jobs.md presubmits: - gojek/feast: + feast-dev/feast: - name: test-core-and-ingestion decorate: true - always_run: true spec: containers: - - image: maven:3.6-jdk-8 - command: [".prow/scripts/test-core-ingestion.sh"] + - image: maven:3.6-jdk-11 + command: ["infra/scripts/test-core-ingestion.sh"] resources: requests: - cpu: "1500m" + cpu: "2000m" memory: "1536Mi" - limit: - memory: "4096Mi" + skip_branches: + - ^v0\.(3|4)-branch$ + + - name: test-core-and-ingestion-java-8 + decorate: true + always_run: true + spec: + containers: + - image: maven:3.6-jdk-8 + command: ["infra/scripts/test-core-ingestion.sh"] + resources: + requests: + cpu: "2000m" + memory: "1536Mi" + branches: + - ^v0\.(3|4)-branch$ - name: test-serving + decorate: true + spec: + containers: + - image: maven:3.6-jdk-11 + command: ["infra/scripts/test-serving.sh"] + skip_branches: + - ^v0\.(3|4)-branch$ + + - name: test-serving-java-8 decorate: true always_run: true spec: containers: - - image: maven:3.6-jdk-8 - command: [".prow/scripts/test-serving.sh"] + - image: maven:3.6-jdk-8 + command: ["infra/scripts/test-serving.sh"] + branches: + - ^v0\.(3|4)-branch$ - name: test-java-sdk + decorate: true + spec: + containers: + - image: maven:3.6-jdk-11 + command: ["infra/scripts/test-java-sdk.sh"] + skip_branches: + - ^v0\.(3|4)-branch$ + + - name: test-java-sdk-java-8 decorate: true always_run: true spec: containers: - - image: maven:3.6-jdk-8 - command: [".prow/scripts/test-java-sdk.sh"] + - image: maven:3.6-jdk-8 + command: ["infra/scripts/test-java-sdk.sh"] + branches: + - ^v0\.(3|4)-branch$ - name: test-python-sdk decorate: true - always_run: true spec: containers: - image: python:3.7 - command: [".prow/scripts/test-python-sdk.sh"] + command: ["infra/scripts/test-python-sdk.sh"] - name: test-golang-sdk decorate: true - always_run: true spec: containers: - image: golang:1.13 - command: [".prow/scripts/test-golang-sdk.sh"] + command: ["infra/scripts/test-golang-sdk.sh"] - name: test-end-to-end decorate: true always_run: true spec: containers: - - image: maven:3.6-jdk-8 - command: [".prow/scripts/test-end-to-end.sh"] + - image: maven:3.6-jdk-11 + command: ["infra/scripts/test-end-to-end.sh"] resources: requests: - cpu: "3000m" - memory: "4096Mi" - limit: + cpu: "6" memory: "6144Mi" + skip_branches: + - ^v0\.(3|4)-branch$ + + - name: test-end-to-end-redis-cluster + decorate: true + always_run: true + spec: + containers: + - image: maven:3.6-jdk-11 + command: ["infra/scripts/test-end-to-end-redis-cluster.sh"] + resources: + requests: + cpu: "6" + memory: "6144Mi" + skip_branches: + - ^v0\.(3|4)-branch$ + + - name: test-end-to-end-java-8 + decorate: true + always_run: true + spec: + containers: + - image: maven:3.6-jdk-8 + command: ["infra/scripts/test-end-to-end.sh"] + resources: + requests: + cpu: "6" + memory: "6144Mi" + branches: + - ^v0\.(3|4)-branch$ - name: test-end-to-end-batch decorate: true @@ -130,20 +192,147 @@ presubmits: secret: secretName: feast-service-account containers: - - image: maven:3.6-jdk-8 - command: [".prow/scripts/test-end-to-end-batch.sh"] + - image: maven:3.6-jdk-11 + command: ["infra/scripts/test-end-to-end-batch.sh"] resources: requests: - cpu: "1000m" - memory: "1024Mi" - limit: - memory: "4096Mi" + cpu: "6" + memory: "6144Mi" volumeMounts: - name: service-account mountPath: "/etc/service-account" + skip_branches: + - ^v0\.(3|4)-branch$ + + - name: test-end-to-end-batch-fs-update + decorate: true + always_run: false + spec: + volumes: + - name: service-account + secret: + secretName: feast-service-account + containers: + - image: maven:3.6-jdk-11 + command: ["infra/scripts/test-end-to-end-batch.sh", "-m", "fs_update"] + resources: + requests: + cpu: "6" + memory: "6144Mi" + volumeMounts: + - name: service-account + mountPath: "/etc/service-account" + skip_branches: + - ^v0\.(3|4)-branch$ + + - name: test-end-to-end-batch-java-8 + decorate: true + always_run: true + spec: + volumes: + - name: service-account + secret: + secretName: feast-service-account + containers: + - image: maven:3.6-jdk-8 + command: ["infra/scripts/test-end-to-end-batch.sh"] + resources: + requests: + cpu: "6" + memory: "6144Mi" + volumeMounts: + - name: service-account + mountPath: "/etc/service-account" + branches: + - ^v0\.(3|4)-branch$ + + - name: test-end-to-end-batch-dataflow + decorate: true + always_run: false + spec: + volumes: + - name: service-account-df + secret: + secretName: feast-e2e-service-account + containers: + - image: maven:3.6-jdk-11 + command: ["infra/scripts/test-end-to-end-batch-dataflow.sh"] + resources: + requests: + cpu: "6" + memory: "6144Mi" + volumeMounts: + - name: service-account-df + mountPath: "/etc/service-account-df" + skip_branches: + - ^v0\.(3|4)-branch$ + + - name: test-end-to-end-batch-dataflow-java-8 + decorate: true + always_run: false + spec: + volumes: + - name: service-account-df + secret: + secretName: feast-e2e-service-account + containers: + - image: maven:3.6-jdk-8 + command: ["infra/scripts/test-end-to-end-batch-dataflow.sh"] + resources: + requests: + cpu: "6" + memory: "6144Mi" + volumeMounts: + - name: service-account-df + mountPath: "/etc/service-account-df" + branches: + - ^v0\.(3|4)-branch$ + + - name: publish-docker-images + decorate: true + always_run: true + spec: + containers: + - image: google/cloud-sdk:273.0.0 + command: + - bash + - -c + - | + infra/scripts/download-maven-cache.sh \ + --archive-uri gs://feast-templocation-kf-feast/.m2.2019-10-24.tar \ + --output-dir $PWD/ + + infra/scripts/publish-docker-image.sh \ + --repository gcr.io/kf-feast/feast-core \ + --tag ${PULL_PULL_SHA:1} \ + --file infra/docker/core/Dockerfile \ + --google-service-account-file /etc/gcloud/service-account.json + + infra/scripts/publish-docker-image.sh \ + --repository gcr.io/kf-feast/feast-serving \ + --tag ${PULL_PULL_SHA:1} \ + --file infra/docker/serving/Dockerfile \ + --google-service-account-file /etc/gcloud/service-account.json + + volumeMounts: + - name: docker-socket + mountPath: /var/run/docker.sock + - name: service-account + mountPath: /etc/gcloud/service-account.json + subPath: service-account.json + readOnly: true + securityContext: + privileged: true + volumes: + - name: docker-socket + hostPath: + path: /var/run/docker.sock + - name: service-account + secret: + secretName: feast-service-account postsubmits: - gojek/feast: + feast-dev/feast: - name: publish-python-sdk decorate: true spec: @@ -153,7 +342,7 @@ postsubmits: - sh - -c - | - .prow/scripts/publish-python-sdk.sh \ + make compile-protos-python && infra/scripts/publish-python-sdk.sh \ --directory-path sdk/python --repository pypi volumeMounts: - name: pypirc @@ -173,11 +362,11 @@ postsubmits: decorate: true spec: containers: - - image: maven:3.6-jdk-8 + - image: maven:3.6-jdk-11 command: - bash - -c - - .prow/scripts/publish-java-sdk.sh --revision ${PULL_BASE_REF:1} + - infra/scripts/publish-java-sdk.sh --revision ${PULL_BASE_REF:1} volumeMounts: - name: gpg-keys mountPath: /etc/gpg @@ -193,10 +382,42 @@ postsubmits: - name: maven-settings secret: secretName: maven-settings + skip_branches: + # Skip version 0.3 and 0.4 + - ^v0\.(3|4)\.(0|[1-9]\d*)(-(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$ + branches: - # Filter on tags with semantic versioning, prefixed with "v" + # Filter on tags with semantic versioning, prefixed with "v". - ^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$ + - name: publish-java-8-sdk + decorate: true + spec: + containers: + - image: maven:3.6-jdk-8 + command: + - bash + - -c + - infra/scripts/publish-java-sdk.sh --revision ${PULL_BASE_REF:1} + volumeMounts: + - name: gpg-keys + mountPath: /etc/gpg + readOnly: true + - name: maven-settings + mountPath: /root/.m2/settings.xml + subPath: settings.xml + readOnly: true + volumes: + - name: gpg-keys + secret: + secretName: gpg-keys + - name: maven-settings + secret: + secretName: maven-settings + branches: + # Filter on tags with semantic versioning, prefixed with "v". v0.3 and v0.4 only. + - ^v0\.(3|4)\.(0|[1-9]\d*)(-(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$ + - name: publish-docker-images decorate: true spec: @@ -206,19 +427,19 @@ postsubmits: - bash - -c - | - .prow/scripts/download-maven-cache.sh \ + infra/scripts/download-maven-cache.sh \ --archive-uri gs://feast-templocation-kf-feast/.m2.2019-10-24.tar \ --output-dir $PWD/ if [ $PULL_BASE_REF == "master" ]; then - .prow/scripts/publish-docker-image.sh \ + infra/scripts/publish-docker-image.sh \ --repository gcr.io/kf-feast/feast-core \ --tag dev \ --file infra/docker/core/Dockerfile \ --google-service-account-file /etc/gcloud/service-account.json - .prow/scripts/publish-docker-image.sh \ + infra/scripts/publish-docker-image.sh \ --repository gcr.io/kf-feast/feast-serving \ --tag dev \ --file infra/docker/serving/Dockerfile \ @@ -232,13 +453,13 @@ postsubmits: else - .prow/scripts/publish-docker-image.sh \ + infra/scripts/publish-docker-image.sh \ --repository gcr.io/kf-feast/feast-core \ --tag ${PULL_BASE_REF:1} \ --file infra/docker/core/Dockerfile \ --google-service-account-file /etc/gcloud/service-account.json - .prow/scripts/publish-docker-image.sh \ + infra/scripts/publish-docker-image.sh \ --repository gcr.io/kf-feast/feast-serving \ --tag ${PULL_BASE_REF:1} \ --file infra/docker/serving/Dockerfile \ @@ -295,7 +516,7 @@ postsubmits: sed -i "/version: /c\version: ${PULL_BASE_REF:1}" infra/charts/feast/charts/feast-serving/Chart.yaml sed -i "/ tag: /c\ tag: ${PULL_BASE_REF:1}" infra/charts/feast/charts/feast-serving/values.yaml - .prow/scripts/sync-helm-charts.sh + infra/scripts/sync-helm-charts.sh volumeMounts: - name: service-account mountPath: /etc/gcloud/service-account.json diff --git a/.prow/plugins.yaml b/.prow/plugins.yaml index 80195222903..bfa55bcf2df 100644 --- a/.prow/plugins.yaml +++ b/.prow/plugins.yaml @@ -1,5 +1,5 @@ plugins: - gojek/feast: + feast-dev/feast: - approve - assign - help @@ -12,6 +12,7 @@ plugins: - wip - trigger - config-updater + - require-matching-label config_updater: maps: @@ -19,7 +20,14 @@ config_updater: name: config external_plugins: - gojek/feast: + feast-dev/feast: - name: needs-rebase events: - pull_request + +require_matching_label: +- missing_label: needs-kind + org: feast-dev + repo: feast + prs: true + regexp: ^kind/ diff --git a/CHANGELOG.md b/CHANGELOG.md index f6ad89e0c0d..5339b8c5f57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,294 @@ # Changelog -## [v0.3.0](https://github.com/gojek/feast/tree/v0.3.0) (2019-11-19) +## [v0.5.0](https://github.com/feast-dev/feast/tree/v0.5.0) (2020-05-19) -[Full Changelog](https://github.com/gojek/feast/compare/v0.1.8...v0.3.0) +[Full Changelog](https://github.com/feast-dev/feast/compare/v0.4.7...v0.5.0) + +**Breaking changes:** + +- Add .proto to packages of Protobuf generated Java classes [\#700](https://github.com/feast-dev/feast/pull/700) ([woop](https://github.com/woop)) +- Add support for feature set updates and remove versions [\#676](https://github.com/feast-dev/feast/pull/676) ([zhilingc](https://github.com/zhilingc)) +- Feast configuration files refactored [\#611](https://github.com/feast-dev/feast/pull/611) ([woop](https://github.com/woop)) + +See [Feast 0.5 Release Issue](https://github.com/feast-dev/feast/issues/527) for more details. + +**Implemented enhancements:** + +- Add general storage API and refactor existing store implementations [\#567](https://github.com/feast-dev/feast/pull/567) ([zhilingc](https://github.com/zhilingc)) +- Add support for feature set updates and remove versions [\#676](https://github.com/feast-dev/feast/pull/676) ([zhilingc](https://github.com/zhilingc)) +- Add unique ingestion id for all batch ingestions [\#656](https://github.com/feast-dev/feast/pull/656) ([zhilingc](https://github.com/zhilingc)) +- Add storage interfaces [\#529](https://github.com/feast-dev/feast/pull/529) ([zhilingc](https://github.com/zhilingc)) +- Add BigQuery storage implementation [\#546](https://github.com/feast-dev/feast/pull/546) ([zhilingc](https://github.com/zhilingc)) +- Add Redis storage implementation [\#547](https://github.com/feast-dev/feast/pull/547) ([zhilingc](https://github.com/zhilingc)) +- Add Support for Redis Cluster [\#502](https://github.com/feast-dev/feast/pull/502) ([lavkesh](https://github.com/lavkesh)) +- Add Ingestion Job management API for Feast Core [\#548](https://github.com/feast-dev/feast/pull/548) ([mrzzy](https://github.com/mrzzy)) +- Add feature and feature set labels for metadata [\#536](https://github.com/feast-dev/feast/pull/536) ([imjuanleonard](https://github.com/imjuanleonard)) +- Update Python SDK so FeatureSet can import Schema from Tensorflow metadata [\#450](https://github.com/feast-dev/feast/pull/450) ([davidheryanto](https://github.com/davidheryanto)) + +**Fixed bugs:** + +- Add feature set status JOB\_STARTING to denote feature sets waiting for job to get to RUNNING state [\#714](https://github.com/feast-dev/feast/pull/714) ([zhilingc](https://github.com/zhilingc)) +- Remove feature set status check for job update requirement [\#708](https://github.com/feast-dev/feast/pull/708) ([khorshuheng](https://github.com/khorshuheng)) +- Fix Feast Core docker image [\#703](https://github.com/feast-dev/feast/pull/703) ([khorshuheng](https://github.com/khorshuheng)) +- Include server port config on the generated application.yml [\#696](https://github.com/feast-dev/feast/pull/696) ([khorshuheng](https://github.com/khorshuheng)) +- Fix typo in all types parquet yml file \(e2e test\) [\#683](https://github.com/feast-dev/feast/pull/683) ([khorshuheng](https://github.com/khorshuheng)) +- Add grpc health probe implementation to core [\#680](https://github.com/feast-dev/feast/pull/680) ([zhilingc](https://github.com/zhilingc)) +- Ensure that generated python code are considered as module [\#679](https://github.com/feast-dev/feast/pull/679) ([khorshuheng](https://github.com/khorshuheng)) +- Fix DataflowJobManager to update existing job instance instead of creating new one [\#678](https://github.com/feast-dev/feast/pull/678) ([zhilingc](https://github.com/zhilingc)) +- Fix config validation for feast.jobs.metrics.host [\#662](https://github.com/feast-dev/feast/pull/662) ([davidheryanto](https://github.com/davidheryanto)) +- Docker compose bug fix [\#661](https://github.com/feast-dev/feast/pull/661) ([woop](https://github.com/woop)) +- Swap join columns [\#647](https://github.com/feast-dev/feast/pull/647) ([zhilingc](https://github.com/zhilingc)) +- Fix Feast Serving not registering its store in Feast Core [\#641](https://github.com/feast-dev/feast/pull/641) ([mrzzy](https://github.com/mrzzy)) +- Kafka producer should raise an exception when it fails to connect to broker [\#636](https://github.com/feast-dev/feast/pull/636) ([junhui096](https://github.com/junhui096)) + +**Merged pull requests:** + +- Change organization from gojek to feast-dev [\#712](https://github.com/feast-dev/feast/pull/712) ([woop](https://github.com/woop)) +- Extract feature set update tests so CI doesn't run it [\#709](https://github.com/feast-dev/feast/pull/709) ([zhilingc](https://github.com/zhilingc)) +- Ensure that batch retrieval tests clean up after themselves [\#704](https://github.com/feast-dev/feast/pull/704) ([zhilingc](https://github.com/zhilingc)) +- Apply default project to rows without project during ingestion [\#701](https://github.com/feast-dev/feast/pull/701) ([zhilingc](https://github.com/zhilingc)) +- Update tests to correct compute region [\#699](https://github.com/feast-dev/feast/pull/699) ([terryyylim](https://github.com/terryyylim)) +- Make Projects optional & Update Feature References [\#693](https://github.com/feast-dev/feast/pull/693) ([mrzzy](https://github.com/mrzzy)) +- Add Java code coverage reporting [\#686](https://github.com/feast-dev/feast/pull/686) ([ches](https://github.com/ches)) +- Update e2e tests to allow non-SNAPSHOT testing [\#672](https://github.com/feast-dev/feast/pull/672) ([woop](https://github.com/woop)) +- Move TFDV stats to higher-numbered protobuf fields [\#669](https://github.com/feast-dev/feast/pull/669) ([ches](https://github.com/ches)) +- Clean up Docker Compose and add test [\#668](https://github.com/feast-dev/feast/pull/668) ([woop](https://github.com/woop)) +- Enable Prow e2e tests by default [\#666](https://github.com/feast-dev/feast/pull/666) ([woop](https://github.com/woop)) +- Add label checking to Prow [\#665](https://github.com/feast-dev/feast/pull/665) ([woop](https://github.com/woop)) +- Upgrade Github Checkout action to v2 [\#660](https://github.com/feast-dev/feast/pull/660) ([khorshuheng](https://github.com/khorshuheng)) +- Fix Redis cluster e2e [\#659](https://github.com/feast-dev/feast/pull/659) ([terryyylim](https://github.com/terryyylim)) +- Split Field model into distinct Feature and Entity objects [\#655](https://github.com/feast-dev/feast/pull/655) ([zhilingc](https://github.com/zhilingc)) +- Use Runner enum type instead of string for Job model [\#651](https://github.com/feast-dev/feast/pull/651) ([ches](https://github.com/ches)) +- JobUpdateTask cleanups [\#650](https://github.com/feast-dev/feast/pull/650) ([ches](https://github.com/ches)) +- Update approvers list [\#648](https://github.com/feast-dev/feast/pull/648) ([khorshuheng](https://github.com/khorshuheng)) +- Update end-to-end test config [\#645](https://github.com/feast-dev/feast/pull/645) ([zhilingc](https://github.com/zhilingc)) +- Fix bigquery config for serving store [\#644](https://github.com/feast-dev/feast/pull/644) ([zhilingc](https://github.com/zhilingc)) +- Fix Dataflow translator bug [\#643](https://github.com/feast-dev/feast/pull/643) ([zhilingc](https://github.com/zhilingc)) +- Fix subscription config and doctests [\#634](https://github.com/feast-dev/feast/pull/634) ([woop](https://github.com/woop)) +- Correct links to why-feast and concepts doc site [\#627](https://github.com/feast-dev/feast/pull/627) ([anderseriksson](https://github.com/anderseriksson)) +- Make error on retrieval of nonexistent feature humanly readable [\#625](https://github.com/feast-dev/feast/pull/625) ([mrzzy](https://github.com/mrzzy)) +- Generate golang code for non-serving protos [\#618](https://github.com/feast-dev/feast/pull/618) ([zhilingc](https://github.com/zhilingc)) +- Regenerate golang code, fix proto comparisons [\#616](https://github.com/feast-dev/feast/pull/616) ([zhilingc](https://github.com/zhilingc)) +- Fix doc building [\#603](https://github.com/feast-dev/feast/pull/603) ([woop](https://github.com/woop)) +- Pin Jupyter Notebook version [\#597](https://github.com/feast-dev/feast/pull/597) ([imjuanleonard](https://github.com/imjuanleonard)) +- Create project if not exists on applyFeatureSet [\#596](https://github.com/feast-dev/feast/pull/596) ([Joostrothweiler](https://github.com/Joostrothweiler)) +- Apply a fixed window before writing row metrics [\#590](https://github.com/feast-dev/feast/pull/590) ([davidheryanto](https://github.com/davidheryanto)) +- Allow tests to run on non-master branches [\#588](https://github.com/feast-dev/feast/pull/588) ([woop](https://github.com/woop)) + +## [v0.4.7](https://github.com/feast-dev/feast/tree/v0.4.7) (2020-03-17) + +[Full Changelog](https://github.com/feast-dev/feast/compare/v0.4.6...v0.4.7) + +**Merged pull requests:** +- Add log4j-web jar to core and serving. [\#498](https://github.com/feast-dev/feast/pull/498) ([Yanson](https://github.com/Yanson)) +- Clear all the futures when sync is called. [\#501](https://github.com/feast-dev/feast/pull/501) ([lavkesh](https://github.com/lavkesh)) +- Encode feature row before storing in Redis [\#530](https://github.com/feast-dev/feast/pull/530) ([khorshuheng](https://github.com/khorshuheng)) +- Remove transaction when listing projects [\#522](https://github.com/feast-dev/feast/pull/522) ([davidheryanto](https://github.com/davidheryanto)) +- Remove unused ingestion deps [\#520](https://github.com/feast-dev/feast/pull/520) ([ches](https://github.com/ches)) +- Parameterize end to end test scripts. [\#433](https://github.com/feast-dev/feast/pull/433) ([Yanson](https://github.com/Yanson)) +- Replacing Jedis With Lettuce in ingestion and serving [\#485](https://github.com/feast-dev/feast/pull/485) ([lavkesh](https://github.com/lavkesh)) + +## [v0.4.6](https://github.com/feast-dev/feast/tree/v0.4.6) (2020-02-26) + +[Full Changelog](https://github.com/feast-dev/feast/compare/v0.4.5...v0.4.6) + +**Merged pull requests:** +- Rename metric name for request latency in feast serving [\#488](https://github.com/feast-dev/feast/pull/488) ([davidheryanto](https://github.com/davidheryanto)) +- Allow use of secure gRPC in Feast Python client [\#459](https://github.com/feast-dev/feast/pull/459) ([Yanson](https://github.com/Yanson)) +- Extend WriteMetricsTransform in Ingestion to write feature value stats to StatsD [\#486](https://github.com/feast-dev/feast/pull/486) ([davidheryanto](https://github.com/davidheryanto)) +- Remove transaction from Ingestion [\#480](https://github.com/feast-dev/feast/pull/480) ([imjuanleonard](https://github.com/imjuanleonard)) +- Fix fastavro version used in Feast to avoid Timestamp delta error [\#490](https://github.com/feast-dev/feast/pull/490) ([davidheryanto](https://github.com/davidheryanto)) +- Fail Spotless formatting check before tests execute [\#487](https://github.com/feast-dev/feast/pull/487) ([ches](https://github.com/ches)) +- Reduce refresh rate of specification refresh in Serving to 10 seconds [\#481](https://github.com/feast-dev/feast/pull/481) ([woop](https://github.com/woop)) + +## [v0.4.5](https://github.com/feast-dev/feast/tree/v0.4.5) (2020-02-14) + +[Full Changelog](https://github.com/feast-dev/feast/compare/v0.4.4...v0.4.5) + +**Merged pull requests:** +- Use bzip2 compressed feature set json as pipeline option [\#466](https://github.com/feast-dev/feast/pull/466) ([khorshuheng](https://github.com/khorshuheng)) +- Make redis key creation more determinisitic [\#471](https://github.com/feast-dev/feast/pull/471) ([zhilingc](https://github.com/zhilingc)) +- Helm Chart Upgrades [\#458](https://github.com/feast-dev/feast/pull/458) ([Yanson](https://github.com/Yanson)) +- Exclude version from grouping [\#441](https://github.com/feast-dev/feast/pull/441) ([khorshuheng](https://github.com/khorshuheng)) +- Use concrete class for AvroCoder compatibility [\#465](https://github.com/feast-dev/feast/pull/465) ([zhilingc](https://github.com/zhilingc)) +- Fix typo in split string length check [\#464](https://github.com/feast-dev/feast/pull/464) ([zhilingc](https://github.com/zhilingc)) +- Update README.md and remove versions from Helm Charts [\#457](https://github.com/feast-dev/feast/pull/457) ([woop](https://github.com/woop)) +- Deduplicate example notebooks [\#456](https://github.com/feast-dev/feast/pull/456) ([woop](https://github.com/woop)) +- Allow users not to set max age for batch retrieval [\#446](https://github.com/feast-dev/feast/pull/446) ([zhilingc](https://github.com/zhilingc)) + +## [v0.4.4](https://github.com/feast-dev/feast/tree/v0.4.4) (2020-01-28) + +[Full Changelog](https://github.com/feast-dev/feast/compare/v0.4.3...v0.4.4) + +**Merged pull requests:** + +- Change RedisBackedJobService to use a connection pool [\#439](https://github.com/feast-dev/feast/pull/439) ([zhilingc](https://github.com/zhilingc)) +- Update GKE installation and chart values to work with 0.4.3 [\#434](https://github.com/feast-dev/feast/pull/434) ([lgvital](https://github.com/lgvital)) +- Remove "resource" concept and the need to specify a kind in feature sets [\#432](https://github.com/feast-dev/feast/pull/432) ([woop](https://github.com/woop)) +- Add retry options to BigQuery [\#431](https://github.com/feast-dev/feast/pull/431) ([Yanson](https://github.com/Yanson)) +- Fix logging [\#430](https://github.com/feast-dev/feast/pull/430) ([Yanson](https://github.com/Yanson)) +- Add documentation for bigquery batch retrieval [\#428](https://github.com/feast-dev/feast/pull/428) ([zhilingc](https://github.com/zhilingc)) +- Publish datatypes/java along with sdk/java [\#426](https://github.com/feast-dev/feast/pull/426) ([ches](https://github.com/ches)) +- Update basic Feast example to Feast 0.4 [\#424](https://github.com/feast-dev/feast/pull/424) ([woop](https://github.com/woop)) +- Introduce datatypes/java module for proto generation [\#391](https://github.com/feast-dev/feast/pull/391) ([ches](https://github.com/ches)) + +## [v0.4.3](https://github.com/feast-dev/feast/tree/v0.4.3) (2020-01-08) + +[Full Changelog](https://github.com/feast-dev/feast/compare/v0.4.2...v0.4.3) + +**Fixed bugs:** + +- Bugfix for redis ingestion retries throwing NullPointerException on remote runners [\#417](https://github.com/feast-dev/feast/pull/417) ([khorshuheng](https://github.com/khorshuheng)) + +## [v0.4.2](https://github.com/feast-dev/feast/tree/v0.4.2) (2020-01-07) + +[Full Changelog](https://github.com/feast-dev/feast/compare/v0.4.1...v0.4.2) + +**Fixed bugs:** + +- Missing argument in error string in ValidateFeatureRowDoFn [\#401](https://github.com/feast-dev/feast/issues/401) + +**Merged pull requests:** + +- Define maven revision property when packaging jars in Dockerfile so the images are built successfully [\#410](https://github.com/feast-dev/feast/pull/410) ([davidheryanto](https://github.com/davidheryanto)) +- Deduplicate rows in subquery [\#409](https://github.com/feast-dev/feast/pull/409) ([zhilingc](https://github.com/zhilingc)) +- Filter out extra fields, deduplicate fields in ingestion [\#404](https://github.com/feast-dev/feast/pull/404) ([zhilingc](https://github.com/zhilingc)) +- Automatic documentation generation for gRPC API [\#403](https://github.com/feast-dev/feast/pull/403) ([woop](https://github.com/woop)) +- Update feast core default values to include hibernate merge strategy [\#400](https://github.com/feast-dev/feast/pull/400) ([zhilingc](https://github.com/zhilingc)) +- Move cli into feast package [\#398](https://github.com/feast-dev/feast/pull/398) ([zhilingc](https://github.com/zhilingc)) +- Use Nexus staging plugin for deployment [\#394](https://github.com/feast-dev/feast/pull/394) ([khorshuheng](https://github.com/khorshuheng)) +- Handle retry for redis io flow [\#274](https://github.com/feast-dev/feast/pull/274) ([khorshuheng](https://github.com/khorshuheng)) + +## [v0.4.1](https://github.com/feast-dev/feast/tree/v0.4.1) (2019-12-30) + +[Full Changelog](https://github.com/feast-dev/feast/compare/v0.4.0...v0.4.1) + +**Merged pull requests:** + +- Add project-related commands to CLI [\#397](https://github.com/feast-dev/feast/pull/397) ([zhilingc](https://github.com/zhilingc)) + +## [v0.4.0](https://github.com/feast-dev/feast/tree/v0.4.0) (2019-12-28) + +[Full Changelog](https://github.com/feast-dev/feast/compare/v0.3.5...v0.4.0) + +**Implemented enhancements:** + +- Edit description in feature specification to also reflect in BigQuery schema description. [\#239](https://github.com/feast-dev/feast/issues/239) +- Allow for disabling of metrics pushing [\#57](https://github.com/feast-dev/feast/issues/57) + +**Merged pull requests:** + +- Java SDK release script [\#406](https://github.com/feast-dev/feast/pull/406) ([davidheryanto](https://github.com/davidheryanto)) +- Use fixed 'dev' revision for test-e2e-batch [\#395](https://github.com/feast-dev/feast/pull/395) ([davidheryanto](https://github.com/davidheryanto)) +- Project Namespacing [\#393](https://github.com/feast-dev/feast/pull/393) ([woop](https://github.com/woop)) +- \\(concepts\): change data types to upper case because lower case … [\#389](https://github.com/feast-dev/feast/pull/389) ([david30907d](https://github.com/david30907d)) +- Remove alpha v1 from java package name [\#387](https://github.com/feast-dev/feast/pull/387) ([khorshuheng](https://github.com/khorshuheng)) +- Minor bug fixes for Python SDK [\#383](https://github.com/feast-dev/feast/pull/383) ([voonhous](https://github.com/voonhous)) +- Allow user to override job options [\#377](https://github.com/feast-dev/feast/pull/377) ([khorshuheng](https://github.com/khorshuheng)) +- Add documentation to default values.yaml in Feast chart [\#376](https://github.com/feast-dev/feast/pull/376) ([davidheryanto](https://github.com/davidheryanto)) +- Add support for file paths for providing entity rows during batch retrieval [\#375](https://github.com/feast-dev/feast/pull/375) ([voonhous](https://github.com/voonhous)) +- Update sync helm chart script to ensure requirements.lock in in sync with requirements.yaml [\#373](https://github.com/feast-dev/feast/pull/373) ([davidheryanto](https://github.com/davidheryanto)) +- Catch errors thrown by BQ during entity table loading [\#371](https://github.com/feast-dev/feast/pull/371) ([zhilingc](https://github.com/zhilingc)) +- Async job management [\#361](https://github.com/feast-dev/feast/pull/361) ([zhilingc](https://github.com/zhilingc)) +- Infer schema of PyArrow table directly [\#355](https://github.com/feast-dev/feast/pull/355) ([voonhous](https://github.com/voonhous)) +- Add readiness checks for Feast services in end to end test [\#337](https://github.com/feast-dev/feast/pull/337) ([davidheryanto](https://github.com/davidheryanto)) +- Create CHANGELOG.md [\#321](https://github.com/feast-dev/feast/pull/321) ([woop](https://github.com/woop)) + +## [v0.3.7](https://github.com/feast-dev/feast/tree/v0.3.7) (2020-05-01) + +[Full Changelog](https://github.com/feast-dev/feast/compare/v0.3.6...v0.3.7) + +**Merged pull requests:** + +- Moved end-to-end test scripts from .prow to infra [\#657](https://github.com/feast-dev/feast/pull/657) ([khorshuheng](https://github.com/khorshuheng)) +- Backported \#566 & \#647 to v0.3 [\#654](https://github.com/feast-dev/feast/pull/654) ([ches](https://github.com/ches)) + +## [v0.3.6](https://github.com/feast-dev/feast/tree/v0.3.6) (2020-01-03) + +**Merged pull requests:** + +[Full Changelog](https://github.com/feast-dev/feast/compare/v0.3.5...v0.3.6) + +- Add support for file paths for providing entity rows during batch retrieval [\#375](https://github.com/feast-dev/feast/pull/375) ([voonhous](https://github.com/voonhous)) + +## [v0.3.5](https://github.com/feast-dev/feast/tree/v0.3.5) (2019-12-26) + +[Full Changelog](https://github.com/feast-dev/feast/compare/v0.3.4...v0.3.5) + +**Merged pull requests:** + +- Always set destination table in BigQuery query config in Feast Batch Serving so it can handle large results [\#392](https://github.com/feast-dev/feast/pull/392) ([davidheryanto](https://github.com/davidheryanto)) + +## [v0.3.4](https://github.com/feast-dev/feast/tree/v0.3.4) (2019-12-23) + +[Full Changelog](https://github.com/feast-dev/feast/compare/v0.3.3...v0.3.4) + +**Merged pull requests:** + +- Make redis key creation more determinisitic [\#380](https://github.com/feast-dev/feast/pull/380) ([zhilingc](https://github.com/zhilingc)) + +## [v0.3.3](https://github.com/feast-dev/feast/tree/v0.3.3) (2019-12-18) + +[Full Changelog](https://github.com/feast-dev/feast/compare/v0.3.2...v0.3.3) + +**Implemented enhancements:** + +- Added Docker Compose for Feast [\#272](https://github.com/feast-dev/feast/issues/272) +- Added ability to check import job status and cancel job through Python SDK [\#194](https://github.com/feast-dev/feast/issues/194) +- Added basic customer transactions example [\#354](https://github.com/feast-dev/feast/pull/354) ([woop](https://github.com/woop)) + +**Merged pull requests:** + +- Added Prow jobs to automate the release of Docker images and Python SDK [\#369](https://github.com/feast-dev/feast/pull/369) ([davidheryanto](https://github.com/davidheryanto)) +- Fixed installation link in README.md [\#368](https://github.com/feast-dev/feast/pull/368) ([Jeffwan](https://github.com/Jeffwan)) +- Fixed Java SDK tests not actually running \(missing dependencies\) [\#366](https://github.com/feast-dev/feast/pull/366) ([woop](https://github.com/woop)) +- Added more batch retrieval tests [\#357](https://github.com/feast-dev/feast/pull/357) ([zhilingc](https://github.com/zhilingc)) +- Python SDK and Feast Core Bug Fixes [\#353](https://github.com/feast-dev/feast/pull/353) ([woop](https://github.com/woop)) +- Updated buildFeatureSets method in Golang SDK [\#351](https://github.com/feast-dev/feast/pull/351) ([davidheryanto](https://github.com/davidheryanto)) +- Python SDK cleanup [\#348](https://github.com/feast-dev/feast/pull/348) ([woop](https://github.com/woop)) +- Broke up queries for point in time correctness joins [\#347](https://github.com/feast-dev/feast/pull/347) ([zhilingc](https://github.com/zhilingc)) +- Exports gRPC call metrics and Feast resource metrics in Core [\#345](https://github.com/feast-dev/feast/pull/345) ([davidheryanto](https://github.com/davidheryanto)) +- Fixed broken Google Group link on Community page [\#343](https://github.com/feast-dev/feast/pull/343) ([ches](https://github.com/ches)) +- Ensured ImportJobTest is not flaky by checking WriteToStore metric and requesting adequate resources for testing [\#332](https://github.com/feast-dev/feast/pull/332) ([davidheryanto](https://github.com/davidheryanto)) +- Added docker-compose file with Jupyter notebook [\#328](https://github.com/feast-dev/feast/pull/328) ([khorshuheng](https://github.com/khorshuheng)) +- Added minimal implementation of ingesting Parquet and CSV files [\#327](https://github.com/feast-dev/feast/pull/327) ([voonhous](https://github.com/voonhous)) + +## [v0.3.2](https://github.com/feast-dev/feast/tree/v0.3.2) (2019-11-29) + +[Full Changelog](https://github.com/feast-dev/feast/compare/v0.3.1...v0.3.2) + +**Merged pull requests:** + +- Fixed incorrect BigQuery schema creation from FeatureSetSpec [\#340](https://github.com/feast-dev/feast/pull/340) ([davidheryanto](https://github.com/davidheryanto)) +- Filtered out feature sets that dont share the same source [\#339](https://github.com/feast-dev/feast/pull/339) ([zhilingc](https://github.com/zhilingc)) +- Changed latency calculation method to not use Timer [\#338](https://github.com/feast-dev/feast/pull/338) ([zhilingc](https://github.com/zhilingc)) +- Moved Prometheus annotations to pod template for serving [\#336](https://github.com/feast-dev/feast/pull/336) ([zhilingc](https://github.com/zhilingc)) +- Removed metrics windowing, cleaned up step names for metrics writing [\#334](https://github.com/feast-dev/feast/pull/334) ([zhilingc](https://github.com/zhilingc)) +- Set BigQuery table time partition inside get table function [\#333](https://github.com/feast-dev/feast/pull/333) ([zhilingc](https://github.com/zhilingc)) +- Added unit test in Redis to return values with no max age set [\#329](https://github.com/feast-dev/feast/pull/329) ([smadarasmi](https://github.com/smadarasmi)) +- Consolidated jobs into single steps instead of branching out [\#326](https://github.com/feast-dev/feast/pull/326) ([zhilingc](https://github.com/zhilingc)) +- Pinned Python SDK to minor versions for dependencies [\#322](https://github.com/feast-dev/feast/pull/322) ([woop](https://github.com/woop)) +- Added Auto format to Google style with Spotless [\#317](https://github.com/feast-dev/feast/pull/317) ([ches](https://github.com/ches)) + +## [v0.3.1](https://github.com/feast-dev/feast/tree/v0.3.1) (2019-11-25) + +[Full Changelog](https://github.com/feast-dev/feast/compare/v0.3.0...v0.3.1) + +**Merged pull requests:** + +- Added Prometheus metrics to serving [\#316](https://github.com/feast-dev/feast/pull/316) ([zhilingc](https://github.com/zhilingc)) +- Changed default job metrics sink to Statsd [\#315](https://github.com/feast-dev/feast/pull/315) ([zhilingc](https://github.com/zhilingc)) +- Fixed module import error in Feast CLI [\#314](https://github.com/feast-dev/feast/pull/314) ([davidheryanto](https://github.com/davidheryanto)) + +## [v0.3.0](https://github.com/feast-dev/feast/tree/v0.3.0) (2019-11-19) + +[Full Changelog](https://github.com/feast-dev/feast/compare/v0.1.8...v0.3.0) **Summary:** -* Introduced "Feature Sets" as a concept with a new [Feast Core API](https://github.com/gojek/feast/blob/v0.3.0/protos/feast/core/CoreService.proto), [Feast Serving API](https://github.com/gojek/feast/blob/v0.3.0/protos/feast/serving/ServingService.proto) -* Upgraded [Python SDK](https://github.com/gojek/feast/tree/v0.3.0/sdk/python) to support new Feast API. Allows for management of Feast as a library or through the command line. -* Implemented a [Golang SDK](https://github.com/gojek/feast/tree/v0.3.0/sdk/go) and [Java SDK](https://github.com/gojek/feast/tree/v0.3.0/sdk/java) to support the new Feast Core and Feast Serving APIs. +* Introduced "Feature Sets" as a concept with a new [Feast Core API](https://github.com/feast-dev/feast/blob/v0.3.0/protos/feast/core/CoreService.proto), [Feast Serving API](https://github.com/feast-dev/feast/blob/v0.3.0/protos/feast/serving/ServingService.proto) +* Upgraded [Python SDK](https://github.com/feast-dev/feast/tree/v0.3.0/sdk/python) to support new Feast API. Allows for management of Feast as a library or through the command line. +* Implemented a [Golang SDK](https://github.com/feast-dev/feast/tree/v0.3.0/sdk/go) and [Java SDK](https://github.com/feast-dev/feast/tree/v0.3.0/sdk/java) to support the new Feast Core and Feast Serving APIs. * Added support for multi-feature set retrieval and joins. * Added point-in-time correct retrieval for both batch and online serving. * Added support for an external source in Kafka. @@ -17,247 +297,247 @@ **Merged pull requests:** -- Regenerate go protos [\#313](https://github.com/gojek/feast/pull/313) ([zhilingc](https://github.com/zhilingc)) -- Bump chart version to 0.3.0 [\#311](https://github.com/gojek/feast/pull/311) ([zhilingc](https://github.com/zhilingc)) -- Refactored Core API: ListFeatureSets, ListStore, and GetFeatureSet [\#309](https://github.com/gojek/feast/pull/309) ([woop](https://github.com/woop)) -- Use Maven's --also-make by default [\#308](https://github.com/gojek/feast/pull/308) ([ches](https://github.com/ches)) -- Python SDK Ingestion and schema inference updates [\#307](https://github.com/gojek/feast/pull/307) ([woop](https://github.com/woop)) -- Batch ingestion fix [\#299](https://github.com/gojek/feast/pull/299) ([zhilingc](https://github.com/zhilingc)) -- Update values-demo.yaml to make Minikube installation simpler [\#298](https://github.com/gojek/feast/pull/298) ([woop](https://github.com/woop)) -- Fix bug in core not setting default Kafka source [\#297](https://github.com/gojek/feast/pull/297) ([woop](https://github.com/woop)) -- Replace Prometheus logging in ingestion with StatsD logging [\#293](https://github.com/gojek/feast/pull/293) ([woop](https://github.com/woop)) -- Feast Core: Stage files manually when launching Dataflow jobs [\#291](https://github.com/gojek/feast/pull/291) ([davidheryanto](https://github.com/davidheryanto)) -- Database tweaks [\#290](https://github.com/gojek/feast/pull/290) ([smadarasmi](https://github.com/smadarasmi)) -- Feast Helm charts and build script [\#289](https://github.com/gojek/feast/pull/289) ([davidheryanto](https://github.com/davidheryanto)) -- Fix max\_age changes not updating specs and add TQDM silencing flag [\#292](https://github.com/gojek/feast/pull/292) ([woop](https://github.com/woop)) -- Ingestion fixes [\#286](https://github.com/gojek/feast/pull/286) ([zhilingc](https://github.com/zhilingc)) -- Consolidate jobs [\#279](https://github.com/gojek/feast/pull/279) ([zhilingc](https://github.com/zhilingc)) -- Import Spring Boot's dependency BOM, fix spring-boot:run at parent project level [\#276](https://github.com/gojek/feast/pull/276) ([ches](https://github.com/ches)) -- Feast 0.3 Continuous Integration \(CI\) Update [\#271](https://github.com/gojek/feast/pull/271) ([davidheryanto](https://github.com/davidheryanto)) -- Add batch feature retrieval to Python SDK [\#268](https://github.com/gojek/feast/pull/268) ([woop](https://github.com/woop)) -- Set Maven build requirements and some project POM metadata [\#267](https://github.com/gojek/feast/pull/267) ([ches](https://github.com/ches)) -- Python SDK enhancements [\#264](https://github.com/gojek/feast/pull/264) ([woop](https://github.com/woop)) -- Use a symlink for Java SDK's protos [\#263](https://github.com/gojek/feast/pull/263) ([ches](https://github.com/ches)) -- Clean up the Maven build [\#262](https://github.com/gojek/feast/pull/262) ([ches](https://github.com/ches)) -- Add golang SDK [\#261](https://github.com/gojek/feast/pull/261) ([zhilingc](https://github.com/zhilingc)) -- Move storage configuration to serving [\#254](https://github.com/gojek/feast/pull/254) ([zhilingc](https://github.com/zhilingc)) -- Serving API changes for 0.3 [\#253](https://github.com/gojek/feast/pull/253) ([zhilingc](https://github.com/zhilingc)) - -## [v0.1.8](https://github.com/gojek/feast/tree/v0.1.8) (2019-10-30) - -[Full Changelog](https://github.com/gojek/feast/compare/v0.1.2...v0.1.8) +- Regenerate go protos [\#313](https://github.com/feast-dev/feast/pull/313) ([zhilingc](https://github.com/zhilingc)) +- Bump chart version to 0.3.0 [\#311](https://github.com/feast-dev/feast/pull/311) ([zhilingc](https://github.com/zhilingc)) +- Refactored Core API: ListFeatureSets, ListStore, and GetFeatureSet [\#309](https://github.com/feast-dev/feast/pull/309) ([woop](https://github.com/woop)) +- Use Maven's --also-make by default [\#308](https://github.com/feast-dev/feast/pull/308) ([ches](https://github.com/ches)) +- Python SDK Ingestion and schema inference updates [\#307](https://github.com/feast-dev/feast/pull/307) ([woop](https://github.com/woop)) +- Batch ingestion fix [\#299](https://github.com/feast-dev/feast/pull/299) ([zhilingc](https://github.com/zhilingc)) +- Update values-demo.yaml to make Minikube installation simpler [\#298](https://github.com/feast-dev/feast/pull/298) ([woop](https://github.com/woop)) +- Fix bug in core not setting default Kafka source [\#297](https://github.com/feast-dev/feast/pull/297) ([woop](https://github.com/woop)) +- Replace Prometheus logging in ingestion with StatsD logging [\#293](https://github.com/feast-dev/feast/pull/293) ([woop](https://github.com/woop)) +- Feast Core: Stage files manually when launching Dataflow jobs [\#291](https://github.com/feast-dev/feast/pull/291) ([davidheryanto](https://github.com/davidheryanto)) +- Database tweaks [\#290](https://github.com/feast-dev/feast/pull/290) ([smadarasmi](https://github.com/smadarasmi)) +- Feast Helm charts and build script [\#289](https://github.com/feast-dev/feast/pull/289) ([davidheryanto](https://github.com/davidheryanto)) +- Fix max\_age changes not updating specs and add TQDM silencing flag [\#292](https://github.com/feast-dev/feast/pull/292) ([woop](https://github.com/woop)) +- Ingestion fixes [\#286](https://github.com/feast-dev/feast/pull/286) ([zhilingc](https://github.com/zhilingc)) +- Consolidate jobs [\#279](https://github.com/feast-dev/feast/pull/279) ([zhilingc](https://github.com/zhilingc)) +- Import Spring Boot's dependency BOM, fix spring-boot:run at parent project level [\#276](https://github.com/feast-dev/feast/pull/276) ([ches](https://github.com/ches)) +- Feast 0.3 Continuous Integration \(CI\) Update [\#271](https://github.com/feast-dev/feast/pull/271) ([davidheryanto](https://github.com/davidheryanto)) +- Add batch feature retrieval to Python SDK [\#268](https://github.com/feast-dev/feast/pull/268) ([woop](https://github.com/woop)) +- Set Maven build requirements and some project POM metadata [\#267](https://github.com/feast-dev/feast/pull/267) ([ches](https://github.com/ches)) +- Python SDK enhancements [\#264](https://github.com/feast-dev/feast/pull/264) ([woop](https://github.com/woop)) +- Use a symlink for Java SDK's protos [\#263](https://github.com/feast-dev/feast/pull/263) ([ches](https://github.com/ches)) +- Clean up the Maven build [\#262](https://github.com/feast-dev/feast/pull/262) ([ches](https://github.com/ches)) +- Add golang SDK [\#261](https://github.com/feast-dev/feast/pull/261) ([zhilingc](https://github.com/zhilingc)) +- Move storage configuration to serving [\#254](https://github.com/feast-dev/feast/pull/254) ([zhilingc](https://github.com/zhilingc)) +- Serving API changes for 0.3 [\#253](https://github.com/feast-dev/feast/pull/253) ([zhilingc](https://github.com/zhilingc)) + +## [v0.1.8](https://github.com/feast-dev/feast/tree/v0.1.8) (2019-10-30) + +[Full Changelog](https://github.com/feast-dev/feast/compare/v0.1.2...v0.1.8) **Implemented enhancements:** -- Feast cli config file should be settable by an env var [\#149](https://github.com/gojek/feast/issues/149) -- Helm chart for deploying feast using Flink as runner [\#64](https://github.com/gojek/feast/issues/64) -- Get ingestion metrics when running on Flink runner [\#63](https://github.com/gojek/feast/issues/63) -- Move source types into their own package and discover them using java.util.ServiceLoader [\#61](https://github.com/gojek/feast/issues/61) -- Change config to yaml [\#51](https://github.com/gojek/feast/issues/51) -- Ability to pass runner option during ingestion job submission [\#50](https://github.com/gojek/feast/issues/50) +- Feast cli config file should be settable by an env var [\#149](https://github.com/feast-dev/feast/issues/149) +- Helm chart for deploying feast using Flink as runner [\#64](https://github.com/feast-dev/feast/issues/64) +- Get ingestion metrics when running on Flink runner [\#63](https://github.com/feast-dev/feast/issues/63) +- Move source types into their own package and discover them using java.util.ServiceLoader [\#61](https://github.com/feast-dev/feast/issues/61) +- Change config to yaml [\#51](https://github.com/feast-dev/feast/issues/51) +- Ability to pass runner option during ingestion job submission [\#50](https://github.com/feast-dev/feast/issues/50) **Fixed bugs:** -- Fix Print Method in Feast CLI [\#211](https://github.com/gojek/feast/issues/211) -- Dataflow monitoring by core is failing with incorrect job id [\#153](https://github.com/gojek/feast/issues/153) -- Feast core crashes without logger set [\#150](https://github.com/gojek/feast/issues/150) +- Fix Print Method in Feast CLI [\#211](https://github.com/feast-dev/feast/issues/211) +- Dataflow monitoring by core is failing with incorrect job id [\#153](https://github.com/feast-dev/feast/issues/153) +- Feast core crashes without logger set [\#150](https://github.com/feast-dev/feast/issues/150) **Merged pull requests:** -- Remove redis transaction [\#280](https://github.com/gojek/feast/pull/280) ([pradithya](https://github.com/pradithya)) -- Fix tracing to continue from existing trace created by grpc client [\#245](https://github.com/gojek/feast/pull/245) ([pradithya](https://github.com/pradithya)) +- Remove redis transaction [\#280](https://github.com/feast-dev/feast/pull/280) ([pradithya](https://github.com/pradithya)) +- Fix tracing to continue from existing trace created by grpc client [\#245](https://github.com/feast-dev/feast/pull/245) ([pradithya](https://github.com/pradithya)) -## [v0.1.2](https://github.com/gojek/feast/tree/v0.1.2) (2019-08-23) +## [v0.1.2](https://github.com/feast-dev/feast/tree/v0.1.2) (2019-08-23) -[Full Changelog](https://github.com/gojek/feast/compare/v0.1.1...v0.1.2) +[Full Changelog](https://github.com/feast-dev/feast/compare/v0.1.1...v0.1.2) **Fixed bugs:** -- Batch Import, feature with datetime format issue [\#203](https://github.com/gojek/feast/issues/203) -- Serving not correctly reporting readiness check if there is no activity [\#190](https://github.com/gojek/feast/issues/190) -- Serving stop periodically reloading feature specification after a while [\#188](https://github.com/gojek/feast/issues/188) +- Batch Import, feature with datetime format issue [\#203](https://github.com/feast-dev/feast/issues/203) +- Serving not correctly reporting readiness check if there is no activity [\#190](https://github.com/feast-dev/feast/issues/190) +- Serving stop periodically reloading feature specification after a while [\#188](https://github.com/feast-dev/feast/issues/188) **Merged pull requests:** -- Add `romanwozniak` to prow owners config [\#216](https://github.com/gojek/feast/pull/216) ([romanwozniak](https://github.com/romanwozniak)) -- Implement filter for create dataset api [\#215](https://github.com/gojek/feast/pull/215) ([pradithya](https://github.com/pradithya)) -- expand raw column to accomodate more features ingested in one go [\#213](https://github.com/gojek/feast/pull/213) ([budi](https://github.com/budi)) -- update feast installation docs [\#210](https://github.com/gojek/feast/pull/210) ([budi](https://github.com/budi)) -- Add Prow job for unit testing Python SDK [\#209](https://github.com/gojek/feast/pull/209) ([davidheryanto](https://github.com/davidheryanto)) -- fix create\_dataset [\#208](https://github.com/gojek/feast/pull/208) ([budi](https://github.com/budi)) -- Update Feast installation doc [\#207](https://github.com/gojek/feast/pull/207) ([davidheryanto](https://github.com/davidheryanto)) -- Fix unit test cli in prow script not returning correct exit code [\#206](https://github.com/gojek/feast/pull/206) ([davidheryanto](https://github.com/davidheryanto)) -- Fix pytests and make TS conversion conditional [\#205](https://github.com/gojek/feast/pull/205) ([zhilingc](https://github.com/zhilingc)) -- Use full prow build id as dataset name during test [\#200](https://github.com/gojek/feast/pull/200) ([davidheryanto](https://github.com/davidheryanto)) -- Add Feast CLI / python SDK documentation [\#199](https://github.com/gojek/feast/pull/199) ([romanwozniak](https://github.com/romanwozniak)) -- Update library version to fix security vulnerabilities in dependencies [\#198](https://github.com/gojek/feast/pull/198) ([davidheryanto](https://github.com/davidheryanto)) -- Update Prow configuration for Feast CI [\#197](https://github.com/gojek/feast/pull/197) ([davidheryanto](https://github.com/davidheryanto)) -- \[budi\] update python sdk quickstart [\#196](https://github.com/gojek/feast/pull/196) ([budi](https://github.com/budi)) -- Readiness probe [\#191](https://github.com/gojek/feast/pull/191) ([pradithya](https://github.com/pradithya)) -- Fix periodic feature spec reload [\#189](https://github.com/gojek/feast/pull/189) ([pradithya](https://github.com/pradithya)) -- Fixed a typo in environment variable in installation [\#187](https://github.com/gojek/feast/pull/187) ([gauravkumar37](https://github.com/gauravkumar37)) -- Revert "Update Quickstart" [\#185](https://github.com/gojek/feast/pull/185) ([zhilingc](https://github.com/zhilingc)) -- Update Quickstart [\#184](https://github.com/gojek/feast/pull/184) ([pradithya](https://github.com/pradithya)) -- Continuous integration and deployment \(CI/CD\) update [\#183](https://github.com/gojek/feast/pull/183) ([davidheryanto](https://github.com/davidheryanto)) -- Remove feature specs being able to declare their serving or warehouse stores [\#159](https://github.com/gojek/feast/pull/159) ([tims](https://github.com/tims)) - -## [v0.1.1](https://github.com/gojek/feast/tree/v0.1.1) (2019-04-18) - -[Full Changelog](https://github.com/gojek/feast/compare/v0.1.0...v0.1.1) +- Add `romanwozniak` to prow owners config [\#216](https://github.com/feast-dev/feast/pull/216) ([romanwozniak](https://github.com/romanwozniak)) +- Implement filter for create dataset api [\#215](https://github.com/feast-dev/feast/pull/215) ([pradithya](https://github.com/pradithya)) +- expand raw column to accomodate more features ingested in one go [\#213](https://github.com/feast-dev/feast/pull/213) ([budi](https://github.com/budi)) +- update feast installation docs [\#210](https://github.com/feast-dev/feast/pull/210) ([budi](https://github.com/budi)) +- Add Prow job for unit testing Python SDK [\#209](https://github.com/feast-dev/feast/pull/209) ([davidheryanto](https://github.com/davidheryanto)) +- fix create\_dataset [\#208](https://github.com/feast-dev/feast/pull/208) ([budi](https://github.com/budi)) +- Update Feast installation doc [\#207](https://github.com/feast-dev/feast/pull/207) ([davidheryanto](https://github.com/davidheryanto)) +- Fix unit test cli in prow script not returning correct exit code [\#206](https://github.com/feast-dev/feast/pull/206) ([davidheryanto](https://github.com/davidheryanto)) +- Fix pytests and make TS conversion conditional [\#205](https://github.com/feast-dev/feast/pull/205) ([zhilingc](https://github.com/zhilingc)) +- Use full prow build id as dataset name during test [\#200](https://github.com/feast-dev/feast/pull/200) ([davidheryanto](https://github.com/davidheryanto)) +- Add Feast CLI / python SDK documentation [\#199](https://github.com/feast-dev/feast/pull/199) ([romanwozniak](https://github.com/romanwozniak)) +- Update library version to fix security vulnerabilities in dependencies [\#198](https://github.com/feast-dev/feast/pull/198) ([davidheryanto](https://github.com/davidheryanto)) +- Update Prow configuration for Feast CI [\#197](https://github.com/feast-dev/feast/pull/197) ([davidheryanto](https://github.com/davidheryanto)) +- \[budi\] update python sdk quickstart [\#196](https://github.com/feast-dev/feast/pull/196) ([budi](https://github.com/budi)) +- Readiness probe [\#191](https://github.com/feast-dev/feast/pull/191) ([pradithya](https://github.com/pradithya)) +- Fix periodic feature spec reload [\#189](https://github.com/feast-dev/feast/pull/189) ([pradithya](https://github.com/pradithya)) +- Fixed a typo in environment variable in installation [\#187](https://github.com/feast-dev/feast/pull/187) ([gauravkumar37](https://github.com/gauravkumar37)) +- Revert "Update Quickstart" [\#185](https://github.com/feast-dev/feast/pull/185) ([zhilingc](https://github.com/zhilingc)) +- Update Quickstart [\#184](https://github.com/feast-dev/feast/pull/184) ([pradithya](https://github.com/pradithya)) +- Continuous integration and deployment \(CI/CD\) update [\#183](https://github.com/feast-dev/feast/pull/183) ([davidheryanto](https://github.com/davidheryanto)) +- Remove feature specs being able to declare their serving or warehouse stores [\#159](https://github.com/feast-dev/feast/pull/159) ([tims](https://github.com/tims)) + +## [v0.1.1](https://github.com/feast-dev/feast/tree/v0.1.1) (2019-04-18) + +[Full Changelog](https://github.com/feast-dev/feast/compare/v0.1.0...v0.1.1) **Fixed bugs:** -- Fix BigQuery query template to retrieve training data [\#182](https://github.com/gojek/feast/pull/182) ([davidheryanto](https://github.com/davidheryanto)) +- Fix BigQuery query template to retrieve training data [\#182](https://github.com/feast-dev/feast/pull/182) ([davidheryanto](https://github.com/davidheryanto)) **Merged pull requests:** -- Add python init files [\#176](https://github.com/gojek/feast/pull/176) ([zhilingc](https://github.com/zhilingc)) -- Change pypi package from Feast to feast [\#173](https://github.com/gojek/feast/pull/173) ([zhilingc](https://github.com/zhilingc)) +- Add python init files [\#176](https://github.com/feast-dev/feast/pull/176) ([zhilingc](https://github.com/zhilingc)) +- Change pypi package from Feast to feast [\#173](https://github.com/feast-dev/feast/pull/173) ([zhilingc](https://github.com/zhilingc)) -## [v0.1.0](https://github.com/gojek/feast/tree/v0.1.0) (2019-04-09) +## [v0.1.0](https://github.com/feast-dev/feast/tree/v0.1.0) (2019-04-09) -[Full Changelog](https://github.com/gojek/feast/compare/v0.0.2...v0.1.0) +[Full Changelog](https://github.com/feast-dev/feast/compare/v0.0.2...v0.1.0) **Implemented enhancements:** -- Removal of storing historical value of feature in serving storage [\#53](https://github.com/gojek/feast/issues/53) -- Remove feature "granularity" and relegate to metadata [\#17](https://github.com/gojek/feast/issues/17) +- Removal of storing historical value of feature in serving storage [\#53](https://github.com/feast-dev/feast/issues/53) +- Remove feature "granularity" and relegate to metadata [\#17](https://github.com/feast-dev/feast/issues/17) **Closed issues:** -- Add ability to name an import job [\#167](https://github.com/gojek/feast/issues/167) -- Ingestion retrying an invalid FeatureRow endlessly [\#163](https://github.com/gojek/feast/issues/163) -- Ability to associate data ingested in Warehouse store to its ingestion job [\#145](https://github.com/gojek/feast/issues/145) -- Missing \(Fixing\) unit test for FeatureRowKafkaIO [\#132](https://github.com/gojek/feast/issues/132) +- Add ability to name an import job [\#167](https://github.com/feast-dev/feast/issues/167) +- Ingestion retrying an invalid FeatureRow endlessly [\#163](https://github.com/feast-dev/feast/issues/163) +- Ability to associate data ingested in Warehouse store to its ingestion job [\#145](https://github.com/feast-dev/feast/issues/145) +- Missing \(Fixing\) unit test for FeatureRowKafkaIO [\#132](https://github.com/feast-dev/feast/issues/132) **Merged pull requests:** -- Catch all kind of exception to avoid retrying [\#171](https://github.com/gojek/feast/pull/171) ([pradithya](https://github.com/pradithya)) -- Integration test [\#170](https://github.com/gojek/feast/pull/170) ([zhilingc](https://github.com/zhilingc)) -- Proto error [\#169](https://github.com/gojek/feast/pull/169) ([pradithya](https://github.com/pradithya)) -- Add --name flag to submit job [\#168](https://github.com/gojek/feast/pull/168) ([pradithya](https://github.com/pradithya)) -- Prevent throwing RuntimeException when invalid proto is received [\#166](https://github.com/gojek/feast/pull/166) ([pradithya](https://github.com/pradithya)) -- Add davidheryanto to OWNER file [\#165](https://github.com/gojek/feast/pull/165) ([pradithya](https://github.com/pradithya)) -- Check validity of event timestamp in ValidateFeatureRowDoFn [\#164](https://github.com/gojek/feast/pull/164) ([pradithya](https://github.com/pradithya)) -- Remove granularity [\#162](https://github.com/gojek/feast/pull/162) ([pradithya](https://github.com/pradithya)) -- Better Kafka test [\#160](https://github.com/gojek/feast/pull/160) ([tims](https://github.com/tims)) -- Simplify and document CLI building steps [\#158](https://github.com/gojek/feast/pull/158) ([thirteen37](https://github.com/thirteen37)) -- Fix link typo in README.md [\#156](https://github.com/gojek/feast/pull/156) ([pradithya](https://github.com/pradithya)) -- Add Feast admin quickstart guide [\#155](https://github.com/gojek/feast/pull/155) ([thirteen37](https://github.com/thirteen37)) -- Pass all specs to ingestion by file [\#154](https://github.com/gojek/feast/pull/154) ([tims](https://github.com/tims)) -- Preload spec in serving cache [\#152](https://github.com/gojek/feast/pull/152) ([pradithya](https://github.com/pradithya)) -- Add job identifier to FeatureRow [\#147](https://github.com/gojek/feast/pull/147) ([mansiib](https://github.com/mansiib)) -- Fix unit tests [\#146](https://github.com/gojek/feast/pull/146) ([mansiib](https://github.com/mansiib)) -- Add thirteen37 to OWNERS [\#144](https://github.com/gojek/feast/pull/144) ([thirteen37](https://github.com/thirteen37)) -- Fix import spec created from Importer.from\_csv [\#143](https://github.com/gojek/feast/pull/143) ([pradithya](https://github.com/pradithya)) -- Regenerate go [\#142](https://github.com/gojek/feast/pull/142) ([zhilingc](https://github.com/zhilingc)) -- Flat JSON for pubsub and text files [\#141](https://github.com/gojek/feast/pull/141) ([tims](https://github.com/tims)) -- Add wait flag for jobs, fix go proto path for dataset service [\#138](https://github.com/gojek/feast/pull/138) ([zhilingc](https://github.com/zhilingc)) -- Fix Python SDK importer's ability to apply features [\#135](https://github.com/gojek/feast/pull/135) ([woop](https://github.com/woop)) -- Refactor stores [\#110](https://github.com/gojek/feast/pull/110) ([tims](https://github.com/tims)) -- Coalesce rows [\#89](https://github.com/gojek/feast/pull/89) ([tims](https://github.com/tims)) -- Remove historical feature in serving store [\#87](https://github.com/gojek/feast/pull/87) ([pradithya](https://github.com/pradithya)) - -## [v0.0.2](https://github.com/gojek/feast/tree/v0.0.2) (2019-03-11) - -[Full Changelog](https://github.com/gojek/feast/compare/v0.0.1...v0.0.2) +- Catch all kind of exception to avoid retrying [\#171](https://github.com/feast-dev/feast/pull/171) ([pradithya](https://github.com/pradithya)) +- Integration test [\#170](https://github.com/feast-dev/feast/pull/170) ([zhilingc](https://github.com/zhilingc)) +- Proto error [\#169](https://github.com/feast-dev/feast/pull/169) ([pradithya](https://github.com/pradithya)) +- Add --name flag to submit job [\#168](https://github.com/feast-dev/feast/pull/168) ([pradithya](https://github.com/pradithya)) +- Prevent throwing RuntimeException when invalid proto is received [\#166](https://github.com/feast-dev/feast/pull/166) ([pradithya](https://github.com/pradithya)) +- Add davidheryanto to OWNER file [\#165](https://github.com/feast-dev/feast/pull/165) ([pradithya](https://github.com/pradithya)) +- Check validity of event timestamp in ValidateFeatureRowDoFn [\#164](https://github.com/feast-dev/feast/pull/164) ([pradithya](https://github.com/pradithya)) +- Remove granularity [\#162](https://github.com/feast-dev/feast/pull/162) ([pradithya](https://github.com/pradithya)) +- Better Kafka test [\#160](https://github.com/feast-dev/feast/pull/160) ([tims](https://github.com/tims)) +- Simplify and document CLI building steps [\#158](https://github.com/feast-dev/feast/pull/158) ([thirteen37](https://github.com/thirteen37)) +- Fix link typo in README.md [\#156](https://github.com/feast-dev/feast/pull/156) ([pradithya](https://github.com/pradithya)) +- Add Feast admin quickstart guide [\#155](https://github.com/feast-dev/feast/pull/155) ([thirteen37](https://github.com/thirteen37)) +- Pass all specs to ingestion by file [\#154](https://github.com/feast-dev/feast/pull/154) ([tims](https://github.com/tims)) +- Preload spec in serving cache [\#152](https://github.com/feast-dev/feast/pull/152) ([pradithya](https://github.com/pradithya)) +- Add job identifier to FeatureRow [\#147](https://github.com/feast-dev/feast/pull/147) ([mansiib](https://github.com/mansiib)) +- Fix unit tests [\#146](https://github.com/feast-dev/feast/pull/146) ([mansiib](https://github.com/mansiib)) +- Add thirteen37 to OWNERS [\#144](https://github.com/feast-dev/feast/pull/144) ([thirteen37](https://github.com/thirteen37)) +- Fix import spec created from Importer.from\_csv [\#143](https://github.com/feast-dev/feast/pull/143) ([pradithya](https://github.com/pradithya)) +- Regenerate go [\#142](https://github.com/feast-dev/feast/pull/142) ([zhilingc](https://github.com/zhilingc)) +- Flat JSON for pubsub and text files [\#141](https://github.com/feast-dev/feast/pull/141) ([tims](https://github.com/tims)) +- Add wait flag for jobs, fix go proto path for dataset service [\#138](https://github.com/feast-dev/feast/pull/138) ([zhilingc](https://github.com/zhilingc)) +- Fix Python SDK importer's ability to apply features [\#135](https://github.com/feast-dev/feast/pull/135) ([woop](https://github.com/woop)) +- Refactor stores [\#110](https://github.com/feast-dev/feast/pull/110) ([tims](https://github.com/tims)) +- Coalesce rows [\#89](https://github.com/feast-dev/feast/pull/89) ([tims](https://github.com/tims)) +- Remove historical feature in serving store [\#87](https://github.com/feast-dev/feast/pull/87) ([pradithya](https://github.com/pradithya)) + +## [v0.0.2](https://github.com/feast-dev/feast/tree/v0.0.2) (2019-03-11) + +[Full Changelog](https://github.com/feast-dev/feast/compare/v0.0.1...v0.0.2) **Implemented enhancements:** -- Coalesce FeatureRows for improved "latest" value consistency in serving stores [\#88](https://github.com/gojek/feast/issues/88) -- Kafka source [\#22](https://github.com/gojek/feast/issues/22) +- Coalesce FeatureRows for improved "latest" value consistency in serving stores [\#88](https://github.com/feast-dev/feast/issues/88) +- Kafka source [\#22](https://github.com/feast-dev/feast/issues/22) **Closed issues:** -- Preload Feast's spec in serving cache [\#151](https://github.com/gojek/feast/issues/151) -- Feast csv data upload job [\#137](https://github.com/gojek/feast/issues/137) -- Blocking call to start feast ingestion job [\#136](https://github.com/gojek/feast/issues/136) -- Python SDK fails to apply feature when submitting job [\#134](https://github.com/gojek/feast/issues/134) -- Default dump format should be changed for Python SDK [\#133](https://github.com/gojek/feast/issues/133) -- Listing resources and finding out system state [\#131](https://github.com/gojek/feast/issues/131) -- Reorganise ingestion store classes to match architecture [\#109](https://github.com/gojek/feast/issues/109) +- Preload Feast's spec in serving cache [\#151](https://github.com/feast-dev/feast/issues/151) +- Feast csv data upload job [\#137](https://github.com/feast-dev/feast/issues/137) +- Blocking call to start feast ingestion job [\#136](https://github.com/feast-dev/feast/issues/136) +- Python SDK fails to apply feature when submitting job [\#134](https://github.com/feast-dev/feast/issues/134) +- Default dump format should be changed for Python SDK [\#133](https://github.com/feast-dev/feast/issues/133) +- Listing resources and finding out system state [\#131](https://github.com/feast-dev/feast/issues/131) +- Reorganise ingestion store classes to match architecture [\#109](https://github.com/feast-dev/feast/issues/109) -## [v0.0.1](https://github.com/gojek/feast/tree/v0.0.1) (2019-02-11) +## [v0.0.1](https://github.com/feast-dev/feast/tree/v0.0.1) (2019-02-11) -[Full Changelog](https://github.com/gojek/feast/compare/ec9def2bbb06dc759538e4424caadd70f548ea64...v0.0.1) +[Full Changelog](https://github.com/feast-dev/feast/compare/ec9def2bbb06dc759538e4424caadd70f548ea64...v0.0.1) **Implemented enhancements:** -- Spring boot CLI logs show up as JSON [\#104](https://github.com/gojek/feast/issues/104) -- Allow for registering feature that doesn't have warehouse store [\#5](https://github.com/gojek/feast/issues/5) +- Spring boot CLI logs show up as JSON [\#104](https://github.com/feast-dev/feast/issues/104) +- Allow for registering feature that doesn't have warehouse store [\#5](https://github.com/feast-dev/feast/issues/5) **Fixed bugs:** -- Error when submitting large import spec [\#125](https://github.com/gojek/feast/issues/125) -- Ingestion is not ignoring unknown feature in streaming source [\#99](https://github.com/gojek/feast/issues/99) -- Vulnerability in dependency \(core - jackson-databind \) [\#92](https://github.com/gojek/feast/issues/92) -- TF file for cloud build trigger broken [\#72](https://github.com/gojek/feast/issues/72) -- Job Execution Failure with NullPointerException [\#46](https://github.com/gojek/feast/issues/46) -- Runtime Dependency Error After Upgrade to Beam 2.9.0 [\#44](https://github.com/gojek/feast/issues/44) -- \[FlinkRunner\] Core should not follow remote flink runner job to completion [\#21](https://github.com/gojek/feast/issues/21) -- Go packages in protos use incorrect repo [\#16](https://github.com/gojek/feast/issues/16) +- Error when submitting large import spec [\#125](https://github.com/feast-dev/feast/issues/125) +- Ingestion is not ignoring unknown feature in streaming source [\#99](https://github.com/feast-dev/feast/issues/99) +- Vulnerability in dependency \(core - jackson-databind \) [\#92](https://github.com/feast-dev/feast/issues/92) +- TF file for cloud build trigger broken [\#72](https://github.com/feast-dev/feast/issues/72) +- Job Execution Failure with NullPointerException [\#46](https://github.com/feast-dev/feast/issues/46) +- Runtime Dependency Error After Upgrade to Beam 2.9.0 [\#44](https://github.com/feast-dev/feast/issues/44) +- \[FlinkRunner\] Core should not follow remote flink runner job to completion [\#21](https://github.com/feast-dev/feast/issues/21) +- Go packages in protos use incorrect repo [\#16](https://github.com/feast-dev/feast/issues/16) **Merged pull requests:** -- Disable test during docker image creation [\#129](https://github.com/gojek/feast/pull/129) ([pradithya](https://github.com/pradithya)) -- Repackage helm chart [\#127](https://github.com/gojek/feast/pull/127) ([pradithya](https://github.com/pradithya)) -- Increase the column size for storing raw import spec [\#126](https://github.com/gojek/feast/pull/126) ([pradithya](https://github.com/pradithya)) -- Update Helm Charts \(Redis, Logging\) [\#123](https://github.com/gojek/feast/pull/123) ([woop](https://github.com/woop)) -- Added LOG\_TYPE environmental variable [\#120](https://github.com/gojek/feast/pull/120) ([woop](https://github.com/woop)) -- Fix missing Redis write [\#119](https://github.com/gojek/feast/pull/119) ([pradithya](https://github.com/pradithya)) -- add logging when error on request feature [\#117](https://github.com/gojek/feast/pull/117) ([pradithya](https://github.com/pradithya)) -- run yarn run build during generate-resource [\#115](https://github.com/gojek/feast/pull/115) ([pradithya](https://github.com/pradithya)) -- Add loadBalancerSourceRanges option for both serving and core [\#114](https://github.com/gojek/feast/pull/114) ([zhilingc](https://github.com/zhilingc)) -- Build master [\#112](https://github.com/gojek/feast/pull/112) ([pradithya](https://github.com/pradithya)) -- Cleanup warning while building proto files [\#108](https://github.com/gojek/feast/pull/108) ([pradithya](https://github.com/pradithya)) -- Embed ui build & packaging into core's build [\#106](https://github.com/gojek/feast/pull/106) ([pradithya](https://github.com/pradithya)) -- Add build badge to README [\#103](https://github.com/gojek/feast/pull/103) ([woop](https://github.com/woop)) -- Ignore features in FeatureRow if it's not requested in import spec [\#101](https://github.com/gojek/feast/pull/101) ([pradithya](https://github.com/pradithya)) -- Add override for serving service static ip [\#100](https://github.com/gojek/feast/pull/100) ([zhilingc](https://github.com/zhilingc)) -- Fix go test [\#97](https://github.com/gojek/feast/pull/97) ([zhilingc](https://github.com/zhilingc)) -- add missing copyright headers and fix test fail due to previous merge [\#95](https://github.com/gojek/feast/pull/95) ([tims](https://github.com/tims)) -- Allow submission of kafka jobs [\#94](https://github.com/gojek/feast/pull/94) ([zhilingc](https://github.com/zhilingc)) -- upgrade jackson databind for security vulnerability [\#93](https://github.com/gojek/feast/pull/93) ([tims](https://github.com/tims)) -- Version revert [\#91](https://github.com/gojek/feast/pull/91) ([zhilingc](https://github.com/zhilingc)) -- Fix validating feature row when the associated feature spec has no warehouse store [\#90](https://github.com/gojek/feast/pull/90) ([pradithya](https://github.com/pradithya)) -- Add get command [\#85](https://github.com/gojek/feast/pull/85) ([zhilingc](https://github.com/zhilingc)) -- Avoid error thrown when no storage for warehouse/serving is registered [\#83](https://github.com/gojek/feast/pull/83) ([pradithya](https://github.com/pradithya)) -- Fix jackson dependency issue [\#82](https://github.com/gojek/feast/pull/82) ([zhilingc](https://github.com/zhilingc)) -- Allow registration of feature without warehouse store [\#80](https://github.com/gojek/feast/pull/80) ([pradithya](https://github.com/pradithya)) -- Remove branch from cloud build trigger [\#79](https://github.com/gojek/feast/pull/79) ([woop](https://github.com/woop)) -- move read transforms into "source" package as FeatureSources [\#74](https://github.com/gojek/feast/pull/74) ([tims](https://github.com/tims)) -- Fix tag regex in tf file [\#73](https://github.com/gojek/feast/pull/73) ([zhilingc](https://github.com/zhilingc)) -- Update charts [\#71](https://github.com/gojek/feast/pull/71) ([mansiib](https://github.com/mansiib)) -- Deduplicate storage ids before we fetch them [\#68](https://github.com/gojek/feast/pull/68) ([tims](https://github.com/tims)) -- Check the size of result against deduplicated request [\#67](https://github.com/gojek/feast/pull/67) ([pradithya](https://github.com/pradithya)) -- Add ability to submit ingestion job using Flink [\#62](https://github.com/gojek/feast/pull/62) ([pradithya](https://github.com/pradithya)) -- Fix vulnerabilities for webpack-dev [\#59](https://github.com/gojek/feast/pull/59) ([budi](https://github.com/budi)) -- Build push [\#56](https://github.com/gojek/feast/pull/56) ([zhilingc](https://github.com/zhilingc)) -- Fix github vulnerability issue with webpack [\#54](https://github.com/gojek/feast/pull/54) ([budi](https://github.com/budi)) -- Only lookup storage specs that we actually need [\#52](https://github.com/gojek/feast/pull/52) ([tims](https://github.com/tims)) -- Link Python SDK RFC to PR and Issue [\#49](https://github.com/gojek/feast/pull/49) ([woop](https://github.com/woop)) -- Python SDK [\#47](https://github.com/gojek/feast/pull/47) ([zhilingc](https://github.com/zhilingc)) -- Update com.google.httpclient to be same as Beam's dependency [\#45](https://github.com/gojek/feast/pull/45) ([pradithya](https://github.com/pradithya)) -- Bump Beam SDK to 2.9.0 [\#43](https://github.com/gojek/feast/pull/43) ([pradithya](https://github.com/pradithya)) -- Add fix for tests failing in docker image [\#40](https://github.com/gojek/feast/pull/40) ([zhilingc](https://github.com/zhilingc)) -- Change error store to be part of configuration instead [\#39](https://github.com/gojek/feast/pull/39) ([zhilingc](https://github.com/zhilingc)) -- Fix location of Prow's Tide configuration [\#35](https://github.com/gojek/feast/pull/35) ([woop](https://github.com/woop)) -- Add testing folder for deploying test infrastructure and running tests [\#34](https://github.com/gojek/feast/pull/34) ([woop](https://github.com/woop)) -- skeleton contributing guide [\#33](https://github.com/gojek/feast/pull/33) ([tims](https://github.com/tims)) -- allow empty string to select a NoOp write transform [\#30](https://github.com/gojek/feast/pull/30) ([tims](https://github.com/tims)) -- Remove packaging ingestion as separate profile \(fix \#28\) [\#29](https://github.com/gojek/feast/pull/29) ([pradithya](https://github.com/pradithya)) -- Change gopath to point to gojek repo [\#26](https://github.com/gojek/feast/pull/26) ([zhilingc](https://github.com/zhilingc)) -- Fixes \#31 - errors during kafka deserializer \(passing\) test execution [\#25](https://github.com/gojek/feast/pull/25) ([baskaranz](https://github.com/baskaranz)) -- Kafka IO fixes [\#23](https://github.com/gojek/feast/pull/23) ([tims](https://github.com/tims)) -- KafkaIO implementation for feast [\#19](https://github.com/gojek/feast/pull/19) ([baskaranz](https://github.com/baskaranz)) -- Return same type string for warehouse and serving NoOp stores [\#18](https://github.com/gojek/feast/pull/18) ([tims](https://github.com/tims)) -- \#12: prefetch specs and validate on job expansion [\#15](https://github.com/gojek/feast/pull/15) ([tims](https://github.com/tims)) -- Added RFC for Feast Python SDK [\#14](https://github.com/gojek/feast/pull/14) ([woop](https://github.com/woop)) -- Add more validation in feature spec registration [\#11](https://github.com/gojek/feast/pull/11) ([pradithya](https://github.com/pradithya)) -- Added rfcs/ folder with readme and template [\#10](https://github.com/gojek/feast/pull/10) ([woop](https://github.com/woop)) -- Expose ui service rpc [\#9](https://github.com/gojek/feast/pull/9) ([pradithya](https://github.com/pradithya)) -- Add Feast overview to README [\#8](https://github.com/gojek/feast/pull/8) ([woop](https://github.com/woop)) -- Directory structure changes [\#7](https://github.com/gojek/feast/pull/7) ([zhilingc](https://github.com/zhilingc)) -- Change register to apply [\#4](https://github.com/gojek/feast/pull/4) ([zhilingc](https://github.com/zhilingc)) -- Empty response handling in serving api [\#3](https://github.com/gojek/feast/pull/3) ([pradithya](https://github.com/pradithya)) -- Proto file fixes [\#1](https://github.com/gojek/feast/pull/1) ([pradithya](https://github.com/pradithya)) +- Disable test during docker image creation [\#129](https://github.com/feast-dev/feast/pull/129) ([pradithya](https://github.com/pradithya)) +- Repackage helm chart [\#127](https://github.com/feast-dev/feast/pull/127) ([pradithya](https://github.com/pradithya)) +- Increase the column size for storing raw import spec [\#126](https://github.com/feast-dev/feast/pull/126) ([pradithya](https://github.com/pradithya)) +- Update Helm Charts \(Redis, Logging\) [\#123](https://github.com/feast-dev/feast/pull/123) ([woop](https://github.com/woop)) +- Added LOG\_TYPE environmental variable [\#120](https://github.com/feast-dev/feast/pull/120) ([woop](https://github.com/woop)) +- Fix missing Redis write [\#119](https://github.com/feast-dev/feast/pull/119) ([pradithya](https://github.com/pradithya)) +- add logging when error on request feature [\#117](https://github.com/feast-dev/feast/pull/117) ([pradithya](https://github.com/pradithya)) +- run yarn run build during generate-resource [\#115](https://github.com/feast-dev/feast/pull/115) ([pradithya](https://github.com/pradithya)) +- Add loadBalancerSourceRanges option for both serving and core [\#114](https://github.com/feast-dev/feast/pull/114) ([zhilingc](https://github.com/zhilingc)) +- Build master [\#112](https://github.com/feast-dev/feast/pull/112) ([pradithya](https://github.com/pradithya)) +- Cleanup warning while building proto files [\#108](https://github.com/feast-dev/feast/pull/108) ([pradithya](https://github.com/pradithya)) +- Embed ui build & packaging into core's build [\#106](https://github.com/feast-dev/feast/pull/106) ([pradithya](https://github.com/pradithya)) +- Add build badge to README [\#103](https://github.com/feast-dev/feast/pull/103) ([woop](https://github.com/woop)) +- Ignore features in FeatureRow if it's not requested in import spec [\#101](https://github.com/feast-dev/feast/pull/101) ([pradithya](https://github.com/pradithya)) +- Add override for serving service static ip [\#100](https://github.com/feast-dev/feast/pull/100) ([zhilingc](https://github.com/zhilingc)) +- Fix go test [\#97](https://github.com/feast-dev/feast/pull/97) ([zhilingc](https://github.com/zhilingc)) +- add missing copyright headers and fix test fail due to previous merge [\#95](https://github.com/feast-dev/feast/pull/95) ([tims](https://github.com/tims)) +- Allow submission of kafka jobs [\#94](https://github.com/feast-dev/feast/pull/94) ([zhilingc](https://github.com/zhilingc)) +- upgrade jackson databind for security vulnerability [\#93](https://github.com/feast-dev/feast/pull/93) ([tims](https://github.com/tims)) +- Version revert [\#91](https://github.com/feast-dev/feast/pull/91) ([zhilingc](https://github.com/zhilingc)) +- Fix validating feature row when the associated feature spec has no warehouse store [\#90](https://github.com/feast-dev/feast/pull/90) ([pradithya](https://github.com/pradithya)) +- Add get command [\#85](https://github.com/feast-dev/feast/pull/85) ([zhilingc](https://github.com/zhilingc)) +- Avoid error thrown when no storage for warehouse/serving is registered [\#83](https://github.com/feast-dev/feast/pull/83) ([pradithya](https://github.com/pradithya)) +- Fix jackson dependency issue [\#82](https://github.com/feast-dev/feast/pull/82) ([zhilingc](https://github.com/zhilingc)) +- Allow registration of feature without warehouse store [\#80](https://github.com/feast-dev/feast/pull/80) ([pradithya](https://github.com/pradithya)) +- Remove branch from cloud build trigger [\#79](https://github.com/feast-dev/feast/pull/79) ([woop](https://github.com/woop)) +- move read transforms into "source" package as FeatureSources [\#74](https://github.com/feast-dev/feast/pull/74) ([tims](https://github.com/tims)) +- Fix tag regex in tf file [\#73](https://github.com/feast-dev/feast/pull/73) ([zhilingc](https://github.com/zhilingc)) +- Update charts [\#71](https://github.com/feast-dev/feast/pull/71) ([mansiib](https://github.com/mansiib)) +- Deduplicate storage ids before we fetch them [\#68](https://github.com/feast-dev/feast/pull/68) ([tims](https://github.com/tims)) +- Check the size of result against deduplicated request [\#67](https://github.com/feast-dev/feast/pull/67) ([pradithya](https://github.com/pradithya)) +- Add ability to submit ingestion job using Flink [\#62](https://github.com/feast-dev/feast/pull/62) ([pradithya](https://github.com/pradithya)) +- Fix vulnerabilities for webpack-dev [\#59](https://github.com/feast-dev/feast/pull/59) ([budi](https://github.com/budi)) +- Build push [\#56](https://github.com/feast-dev/feast/pull/56) ([zhilingc](https://github.com/zhilingc)) +- Fix github vulnerability issue with webpack [\#54](https://github.com/feast-dev/feast/pull/54) ([budi](https://github.com/budi)) +- Only lookup storage specs that we actually need [\#52](https://github.com/feast-dev/feast/pull/52) ([tims](https://github.com/tims)) +- Link Python SDK RFC to PR and Issue [\#49](https://github.com/feast-dev/feast/pull/49) ([woop](https://github.com/woop)) +- Python SDK [\#47](https://github.com/feast-dev/feast/pull/47) ([zhilingc](https://github.com/zhilingc)) +- Update com.google.httpclient to be same as Beam's dependency [\#45](https://github.com/feast-dev/feast/pull/45) ([pradithya](https://github.com/pradithya)) +- Bump Beam SDK to 2.9.0 [\#43](https://github.com/feast-dev/feast/pull/43) ([pradithya](https://github.com/pradithya)) +- Add fix for tests failing in docker image [\#40](https://github.com/feast-dev/feast/pull/40) ([zhilingc](https://github.com/zhilingc)) +- Change error store to be part of configuration instead [\#39](https://github.com/feast-dev/feast/pull/39) ([zhilingc](https://github.com/zhilingc)) +- Fix location of Prow's Tide configuration [\#35](https://github.com/feast-dev/feast/pull/35) ([woop](https://github.com/woop)) +- Add testing folder for deploying test infrastructure and running tests [\#34](https://github.com/feast-dev/feast/pull/34) ([woop](https://github.com/woop)) +- skeleton contributing guide [\#33](https://github.com/feast-dev/feast/pull/33) ([tims](https://github.com/tims)) +- allow empty string to select a NoOp write transform [\#30](https://github.com/feast-dev/feast/pull/30) ([tims](https://github.com/tims)) +- Remove packaging ingestion as separate profile \(fix \#28\) [\#29](https://github.com/feast-dev/feast/pull/29) ([pradithya](https://github.com/pradithya)) +- Change gopath to point to feast-dev repo [\#26](https://github.com/feast-dev/feast/pull/26) ([zhilingc](https://github.com/zhilingc)) +- Fixes \#31 - errors during kafka deserializer \(passing\) test execution [\#25](https://github.com/feast-dev/feast/pull/25) ([baskaranz](https://github.com/baskaranz)) +- Kafka IO fixes [\#23](https://github.com/feast-dev/feast/pull/23) ([tims](https://github.com/tims)) +- KafkaIO implementation for feast [\#19](https://github.com/feast-dev/feast/pull/19) ([baskaranz](https://github.com/baskaranz)) +- Return same type string for warehouse and serving NoOp stores [\#18](https://github.com/feast-dev/feast/pull/18) ([tims](https://github.com/tims)) +- \#12: prefetch specs and validate on job expansion [\#15](https://github.com/feast-dev/feast/pull/15) ([tims](https://github.com/tims)) +- Added RFC for Feast Python SDK [\#14](https://github.com/feast-dev/feast/pull/14) ([woop](https://github.com/woop)) +- Add more validation in feature spec registration [\#11](https://github.com/feast-dev/feast/pull/11) ([pradithya](https://github.com/pradithya)) +- Added rfcs/ folder with readme and template [\#10](https://github.com/feast-dev/feast/pull/10) ([woop](https://github.com/woop)) +- Expose ui service rpc [\#9](https://github.com/feast-dev/feast/pull/9) ([pradithya](https://github.com/pradithya)) +- Add Feast overview to README [\#8](https://github.com/feast-dev/feast/pull/8) ([woop](https://github.com/woop)) +- Directory structure changes [\#7](https://github.com/feast-dev/feast/pull/7) ([zhilingc](https://github.com/zhilingc)) +- Change register to apply [\#4](https://github.com/feast-dev/feast/pull/4) ([zhilingc](https://github.com/zhilingc)) +- Empty response handling in serving api [\#3](https://github.com/feast-dev/feast/pull/3) ([pradithya](https://github.com/pradithya)) +- Proto file fixes [\#1](https://github.com/feast-dev/feast/pull/1) ([pradithya](https://github.com/pradithya)) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index eb38db30080..00000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,334 +0,0 @@ -# Contributing Guide - -## Getting Started - -The following guide will help you quickly run Feast in your local machine. - -The main components of Feast are: -- **Feast Core** handles FeatureSpec registration, starts and monitors Ingestion - jobs and ensures that Feast internal metadata is consistent. -- **Feast Ingestion** subscribes to streams of FeatureRow and writes the feature - values to registered Stores. -- **Feast Serving** handles requests for features values retrieval from the end users. - -![Feast Components Overview](docs/assets/feast-components-overview.png) - -**Pre-requisites** -- Java SDK version 8 -- Python version 3.6 (or above) and pip -- Access to Postgres database (version 11 and above) -- Access to [Redis](https://redis.io/topics/quickstart) instance (tested on version 5.x) -- Access to [Kafka](https://kafka.apache.org/) brokers (tested on version 2.x) -- [Maven ](https://maven.apache.org/install.html) version 3.6.x -- [grpc_cli](https://github.com/grpc/grpc/blob/master/doc/command_line_tool.md) - is useful for debugging and quick testing -- An overview of Feast specifications and [protos](./protos/feast) - -> **Assumptions:** -> -> 1. Postgres is running in "localhost:5432" and has a database called "postgres" which -> can be accessed with credentials user "postgres" and password "password". -> To use different database name and credentials, please update -> "$FEAST_HOME/core/src/main/resources/application.yml" -> or set these environment variables: DB_HOST, DB_USERNAME, DB_PASSWORD. -> 2. Redis is running locally and accessible from "localhost:6379" -> 3. Feast has admin access to BigQuery. - - -``` -# Clone Feast branch 0.3-dev -# $FEAST_HOME will refer to be the root directory of this Feast Git repository - -git clone -b 0.3-dev https://github.com/gojek/feast -cd feast -``` - -#### Starting Feast Core - -``` -# Please check the default configuration for Feast Core in -# "$FEAST_HOME/core/src/main/resources/application.yml" and update it accordingly. -# -# Start Feast Core GRPC server on localhost:6565 -mvn --projects core spring-boot:run - -# If Feast Core starts successfully, verify the correct Stores are registered -# correctly, for example by using grpc_cli. -grpc_cli call localhost:6565 GetStores '' - -# Should return something similar to the following. -# Note that you should change BigQuery projectId and datasetId accordingly -# in "$FEAST_HOME/core/src/main/resources/application.yml" - -store { - name: "SERVING" - type: REDIS - subscriptions { - project: "*" - name: "*" - version: "*" - } - redis_config { - host: "localhost" - port: 6379 - } -} -store { - name: "WAREHOUSE" - type: BIGQUERY - subscriptions { - project: "*" - name: "*" - version: "*" - } - bigquery_config { - project_id: "my-google-project-id" - dataset_id: "my-bigquery-dataset-id" - } -} -``` - -#### Starting Feast Serving - -Feast Serving requires administrators to provide an **existing** store name in Feast. -An instance of Feast Serving can only retrieve features from a **single** store. -> In order to retrieve features from multiple stores you must start **multiple** -instances of Feast serving. If you start multiple Feast serving on a single host, -make sure that they are listening on different ports. - -``` -# Start Feast Serving GRPC server on localhost:6566 with store name "SERVING" -mvn --projects serving spring-boot:run -Dspring-boot.run.arguments='--feast.store-name=SERVING' - -# To verify Feast Serving starts successfully -grpc_cli call localhost:6566 GetFeastServingType '' - -# Should return something similar to the following. -type: FEAST_SERVING_TYPE_ONLINE -``` - - -#### Registering a FeatureSet - -Create a new FeatureSet on Feast by sending a request to Feast Core. When a -feature set is successfully registered, Feast Core will start an **ingestion** job -that listens for new features in the FeatureSet. Note that Feast currently only -supports source of type "KAFKA", so you must have access to a running Kafka broker -to register a FeatureSet successfully. - -``` -# Example of registering a new driver feature set -# Note the source value, it assumes that you have access to a Kafka broker -# running on localhost:9092 - -grpc_cli call localhost:6565 ApplyFeatureSet ' -feature_set { - name: "driver" - version: 1 - - entities { - name: "driver_id" - value_type: INT64 - } - - features { - name: "city" - value_type: STRING - } - - source { - type: KAFKA - kafka_source_config { - bootstrap_servers: "localhost:9092" - } - } -} -' - -# To check that the FeatureSet has been registered correctly. -# You should also see logs from Feast Core of the ingestion job being started -grpc_cli call localhost:6565 GetFeatureSets '' -``` - - -#### Ingestion and Population of Feature Values - -``` -# Produce FeatureRow messages to Kafka so it will be ingested by Feast -# and written to the registered stores. -# Make sure the value here is the topic assigned to the feature set -# ... producer.send("feast-driver-features" ...) -# -# Install Python SDK to help writing FeatureRow messages to Kafka -cd $FEAST_HOME/sdk/python -pip3 install -e . -pip3 install pendulum - -# Produce FeatureRow messages to Kafka so it will be ingested by Feast -# and written to the corresponding store. -# Make sure the value here is the topic assigned to the feature set -# ... producer.send("feast-test_feature_set-features" ...) -python3 - < Tool Windows > Maven` -1. Drill down to e.g. `Feast Core > Plugins > spring-boot:run`, right-click and `Create 'feast-core [spring-boot'…` -1. In the dialog that pops up, check the `Resolve Workspace artifacts` box -1. Click `OK`. You should now be able to select this run configuration for the Play button in the main toolbar, keyboard shortcuts, etc. - -[idea-boot-main]: https://stackoverflow.com/questions/30237768/run-spring-boots-main-using-ide - -#### Tips for Running Postgres, Redis and Kafka with Docker - -This guide assumes you are running Docker service on a bridge network (which -is usually the case if you're running Linux). Otherwise, you may need to -use different network options than shown below. - -> `--net host` usually only works as expected when you're running Docker -> service in bridge networking mode. - -``` -# Start Postgres -docker run --name postgres --rm -it -d --net host -e POSTGRES_DB=postgres -e POSTGRES_USER=postgres \ --e POSTGRES_PASSWORD=password postgres:12-alpine - -# Start Redis -docker run --name redis --rm -it --net host -d redis:5-alpine - -# Start Zookeeper (needed by Kafka) -docker run --rm \ - --net=host \ - --name=zookeeper \ - --env=ZOOKEEPER_CLIENT_PORT=2181 \ - --detach confluentinc/cp-zookeeper:5.2.1 - -# Start Kafka -docker run --rm \ - --net=host \ - --name=kafka \ - --env=KAFKA_ZOOKEEPER_CONNECT=localhost:2181 \ - --env=KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 \ - --env=KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 \ - --detach confluentinc/cp-kafka:5.2.1 -``` - -## Code reviews - -Code submission to Feast (including submission from project maintainers) requires review and approval. -Please submit a **pull request** to initiate the code review process. We use [prow](https://github.com/kubernetes/test-infra/tree/master/prow) to manage the testing and reviewing of pull requests. Please refer to [config.yaml](../.prow/config.yaml) for details on the test jobs. - -## Code conventions - -### Java - -We conform to the [Google Java Style Guide]. Maven can helpfully take care of -that for you before you commit: - - $ mvn spotless:apply - -Formatting will be checked automatically during the `verify` phase. This can be -skipped temporarily: - - $ mvn spotless:check # Check is automatic upon `mvn verify` - $ mvn verify -Dspotless.check.skip - -If you're using IntelliJ, you can import [these code style settings][G -IntelliJ] if you'd like to use the IDE's reformat function as you work. - -### Go - -Make sure you apply `go fmt`. - -[Google Java Style Guide]: https://google.github.io/styleguide/javaguide.html -[G IntelliJ]: https://github.com/google/styleguide/blob/gh-pages/intellij-java-google-style.xml diff --git a/Makefile b/Makefile index de61fe2892f..1f41ad09049 100644 --- a/Makefile +++ b/Makefile @@ -14,42 +14,151 @@ # limitations under the License. # -PROJECT_ROOT := $(shell git rev-parse --show-toplevel) +ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) +PROTO_TYPE_SUBDIRS = core serving types storage +PROTO_SERVICE_SUBDIRS = core serving -test: - mvn test +# General + +format: format-python format-go format-java + +lint: lint-python lint-go lint-java + +test: test-python test-java test-go + +protos: compile-protos-go compile-protos-python compile-protos-docs + +build: protos build-java build-docker build-html + +install-ci-dependencies: install-python-ci-dependencies install-go-ci-dependencies install-java-ci-dependencies + +# Java + +install-java-ci-dependencies: + mvn verify clean --fail-never + +format-java: + mvn spotless:apply -test-integration: - $(MAKE) -C testing/integration test-integration TYPE=$(TYPE) ID=$(ID) +lint-java: + mvn spotless:check -build-proto: - $(MAKE) -C protos gen-go - $(MAKE) -C protos gen-python - $(MAKE) -C protos gen-docs +test-java: + mvn test -build-cli: - $(MAKE) build-proto - $(MAKE) -C cli build-all +test-java-with-coverage: + mvn test jacoco:report-aggregate build-java: mvn clean verify -build-docker: - docker build -t $(REGISTRY)/feast-core:$(VERSION) -f infra/docker/core/Dockerfile . - docker build -t $(REGISTRY)/feast-serving:$(VERSION) -f infra/docker/serving/Dockerfile . +# Python SDK + +install-python-ci-dependencies: + pip install -r sdk/python/requirements-ci.txt + +compile-protos-python: install-python-ci-dependencies + @$(foreach dir,$(PROTO_TYPE_SUBDIRS),cd ${ROOT_DIR}/protos; python -m grpc_tools.protoc -I. --python_out=../sdk/python/ --mypy_out=../sdk/python/ feast/$(dir)/*.proto;) + @$(foreach dir,$(PROTO_SERVICE_SUBDIRS),cd ${ROOT_DIR}/protos; python -m grpc_tools.protoc -I. --grpc_python_out=../sdk/python/ feast/$(dir)/*.proto;) + cd ${ROOT_DIR}/protos; python -m grpc_tools.protoc -I. --python_out=../sdk/python/ --mypy_out=../sdk/python/ tensorflow_metadata/proto/v0/*.proto + +install-python: compile-protos-python + pip install -e sdk/python --upgrade + +test-python: + pytest --verbose --color=yes sdk/python/tests + +format-python: + cd ${ROOT_DIR}/sdk/python; isort -rc feast tests + cd ${ROOT_DIR}/sdk/python; black --target-version py37 feast tests + +lint-python: + # TODO: This mypy test needs to be re-enabled and all failures fixed + #cd ${ROOT_DIR}/sdk/python; mypy feast/ tests/ + cd ${ROOT_DIR}/sdk/python; flake8 feast/ tests/ + cd ${ROOT_DIR}/sdk/python; black --check feast tests + +# Go SDK + +install-go-ci-dependencies: + go get -u github.com/golang/protobuf/protoc-gen-go + go get -u golang.org/x/lint/golint + +compile-protos-go: install-go-ci-dependencies + cd ${ROOT_DIR}/protos; protoc -I/usr/local/include -I. --go_out=plugins=grpc,paths=source_relative:../sdk/go/protos/ tensorflow_metadata/proto/v0/*.proto + $(foreach dir,types serving core storage,cd ${ROOT_DIR}/protos; protoc -I/usr/local/include -I. --go_out=plugins=grpc,paths=source_relative:../sdk/go/protos feast/$(dir)/*.proto;) + +test-go: + cd ${ROOT_DIR}/sdk/go; go test ./... + +format-go: + cd ${ROOT_DIR}/sdk/go; gofmt -s -w *.go + +lint-go: + cd ${ROOT_DIR}/sdk/go; go vet + +# Docker build-push-docker: @$(MAKE) build-docker registry=$(REGISTRY) version=$(VERSION) + @$(MAKE) push-core-docker registry=$(REGISTRY) version=$(VERSION) + @$(MAKE) push-serving-docker registry=$(REGISTRY) version=$(VERSION) + @$(MAKE) push-ci-docker registry=$(REGISTRY) + +build-docker: build-core-docker build-serving-docker build-ci-docker + +push-core-docker: docker push $(REGISTRY)/feast-core:$(VERSION) + +push-serving-docker: docker push $(REGISTRY)/feast-serving:$(VERSION) +push-ci-docker: + docker push $(REGISTRY)/feast-ci:latest + +build-core-docker: + docker build -t $(REGISTRY)/feast-core:$(VERSION) -f infra/docker/core/Dockerfile . + +build-serving-docker: + docker build -t $(REGISTRY)/feast-serving:$(VERSION) -f infra/docker/serving/Dockerfile . + +build-ci-docker: + docker build -t $(REGISTRY)/feast-ci:latest -f infra/docker/ci/Dockerfile . + +# Documentation + +install-dependencies-proto-docs: + cd ${ROOT_DIR}/protos; + mkdir -p $$HOME/bin + mkdir -p $$HOME/include + go get github.com/golang/protobuf/proto && \ + go get github.com/russross/blackfriday/v2 && \ + cd $$(mktemp -d) && \ + git clone https://github.com/istio/tools/ && \ + cd tools/cmd/protoc-gen-docs && \ + go build && \ + cp protoc-gen-docs $$HOME/bin && \ + cd $$HOME && curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v3.11.2/protoc-3.11.2-linux-x86_64.zip && \ + unzip protoc-3.11.2-linux-x86_64.zip -d protoc3 && \ + mv protoc3/bin/* $$HOME/bin/ && \ + chmod +x $$HOME/bin/protoc && \ + mv protoc3/include/* $$HOME/include + +compile-protos-docs: + cd ${ROOT_DIR}/protos; protoc --docs_out=../dist/grpc feast/*/*.proto || \ + cd ${ROOT_DIR}; $(MAKE) install-dependencies-proto-docs && cd ${ROOT_DIR}/protos; PATH=$$HOME/bin:$$PATH protoc -I $$HOME/include/ -I . --docs_out=../dist/grpc feast/*/*.proto + clean-html: - rm -rf $(PROJECT_ROOT)/dist - -build-html: - rm -rf $(PROJECT_ROOT)/dist/ - mkdir -p $(PROJECT_ROOT)/dist/python - mkdir -p $(PROJECT_ROOT)/dist/grpc - cd $(PROJECT_ROOT)/protos && $(MAKE) gen-docs - cd $(PROJECT_ROOT)/sdk/python/docs && $(MAKE) html - cp -r $(PROJECT_ROOT)/sdk/python/docs/html/* $(PROJECT_ROOT)/dist/python \ No newline at end of file + rm -rf $(ROOT_DIR)/dist + +build-html: clean-html + mkdir -p $(ROOT_DIR)/dist/python + mkdir -p $(ROOT_DIR)/dist/grpc + + # Build Protobuf documentation + $(MAKE) compile-protos-docs + + # Build Python SDK documentation + $(MAKE) compile-protos-python + cd $(ROOT_DIR)/sdk/python/docs && $(MAKE) html + cp -r $(ROOT_DIR)/sdk/python/docs/html/* $(ROOT_DIR)/dist/python diff --git a/OWNERS b/OWNERS index cfc3fe4ee89..408d4a0d51a 100644 --- a/OWNERS +++ b/OWNERS @@ -4,6 +4,7 @@ approvers: - woop - thirteen37 - davidheryanto + - khorshuheng reviewers: - zhilingc - woop diff --git a/README.md b/README.md index d9b16748266..3da4109f9b8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # Feast - Feature Store for Machine Learning +[![Unit Tests](https://github.com/feast-dev/feast/workflows/unit%20tests/badge.svg?branch=master)](https://github.com/feast-dev/feast/actions?query=workflow%3A%22unit+tests%22+branch%3Amaster) +[![Code Standards](https://github.com/feast-dev/feast/workflows/code%20standards/badge.svg?branch=master)](https://github.com/feast-dev/feast/actions?query=workflow%3A%22code+standards%22+branch%3Amaster) +[![Docs latest](https://img.shields.io/badge/Docs-latest-blue.svg)](https://docs.feast.dev/) +[![GitHub Release](https://img.shields.io/github/release/feast-dev/feast.svg?style=flat)](https://github.com/feast-dev/feast/releases) + ## Overview Feast (Feature Store) is a tool for managing and serving machine learning features. Feast is the bridge between models and data. @@ -28,12 +33,31 @@ my_model = ml.fit(data) prediction = my_model.predict(fs.get_online_features(customer_features, customer_entities)) ``` +## Getting Started with Docker Compose +The following commands will start Feast in online-only mode. +``` +git clone https://github.com/feast-dev/feast.git +cd feast/infra/docker-compose +cp .env.sample .env +docker-compose -f docker-compose.yml -f docker-compose.online.yml up -d +``` + +This will start a local Feast deployment with online serving. Additionally, a [Jupyter Notebook](http://localhost:8888/tree/feast/examples) with Feast examples. + +Please see the links below to set up Feast for batch/historical serving with BigQuery. + ## Important resources - * [Why Feast?](docs/why-feast.md) - * [Concepts](docs/concepts.md) - * [Installation](docs/getting-started/installing-feast.md) - * [Getting Help](docs/community.md) + +Please refer to the official documentation at + + * [Why Feast?](https://docs.feast.dev/introduction/why-feast) + * [Concepts](https://docs.feast.dev/concepts/concepts) + * [Installation](https://docs.feast.dev/installation/overview) + * [Examples](https://github.com/feast-dev/feast/blob/master/examples/) + * [Roadmap](https://docs.feast.dev/roadmap) + * [Change Log](https://github.com/feast-dev/feast/blob/master/CHANGELOG.md) + * [Slack (#Feast)](https://join.slack.com/t/kubeflow/shared_invite/zt-cpr020z4-PfcAue_2nw67~iIDy7maAQ) ## Notice -Feast is a community project and is still under active development. Your feedback and contributions are important to us. Please have a look at our [contributing guide](CONTRIBUTING.md) for details. +Feast is a community project and is still under active development. Your feedback and contributions are important to us. Please have a look at our [contributing guide](docs/contributing/contributing.md) for details. diff --git a/core/pom.xml b/core/pom.xml index 954c7c00185..2f616d5ed41 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -32,16 +32,25 @@ + + org.jacoco + jacoco-maven-plugin + + org.springframework.boot spring-boot-maven-plugin false - - - org.xolstice.maven.plugins - protobuf-maven-plugin + + + build-info + + build-info + + + @@ -76,6 +85,10 @@ org.springframework.boot spring-boot-starter-log4j2 + + org.apache.logging.log4j + log4j-web + io.github.lognet @@ -187,7 +200,6 @@ org.mockito mockito-core - 2.23.0 test @@ -201,5 +213,27 @@ spring-boot-test-autoconfigure test + + + javax.xml.bind + jaxb-api + + + + javax.validation + validation-api + 2.0.0.Final + + + org.hibernate.validator + hibernate-validator + 6.1.2.Final + + + org.hibernate.validator + hibernate-validator-annotation-processor + 6.1.2.Final + + diff --git a/core/src/main/java/feast/core/config/FeastProperties.java b/core/src/main/java/feast/core/config/FeastProperties.java index 1887caf5e64..eb50728baf5 100644 --- a/core/src/main/java/feast/core/config/FeastProperties.java +++ b/core/src/main/java/feast/core/config/FeastProperties.java @@ -16,52 +16,225 @@ */ package feast.core.config; -import java.util.Map; +import feast.core.config.FeastProperties.StreamProperties.FeatureStreamOptions; +import feast.core.validators.OneOfStrings; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.*; +import javax.annotation.PostConstruct; +import javax.validation.*; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Positive; import lombok.Getter; import lombok.Setter; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.info.BuildProperties; @Getter @Setter @ConfigurationProperties(prefix = "feast", ignoreInvalidFields = true) public class FeastProperties { - private String version; - private JobProperties jobs; + /** + * Instantiates a new Feast properties. + * + * @param buildProperties Feast build properties + */ + @Autowired + public FeastProperties(BuildProperties buildProperties) { + setVersion(buildProperties.getVersion()); + } + + /** Instantiates a new Feast properties. */ + public FeastProperties() {} + + /* Feast Core Build Version */ + @NotBlank private String version = "unknown"; + + /* Population job properties */ + @NotNull private JobProperties jobs; + + @NotNull + /* Feast Kafka stream properties */ private StreamProperties stream; + /** Feast job properties. These properties are used for ingestion jobs. */ @Getter @Setter public static class JobProperties { - private String runner; - private Map options; + @NotBlank + /* The active Apache Beam runner name. This name references one instance of the Runner class */ + private String activeRunner; + + /** List of configured job runners. */ + private List runners = new ArrayList<>(); + + /** + * Gets a {@link Runner} instance of the active runner + * + * @return the active runner + */ + public Runner getActiveRunner() { + for (Runner runner : getRunners()) { + if (activeRunner.equals(runner.getName())) { + return runner; + } + } + throw new RuntimeException( + String.format( + "Active runner is misconfigured. Could not find runner: %s.", activeRunner)); + } + + /** Job Runner class. */ + @Getter + @Setter + public static class Runner { + + /** Job runner name. This must be unique. */ + String name; + + /** Job runner type DirectRunner, DataflowRunner currently supported */ + String type; + + /** + * Job runner configuration options. See the following for options + * https://api.docs.feast.dev/grpc/feast.core.pb.html#Runner + */ + Map options = new HashMap<>(); + + /** + * Gets the job runner type as an enum. + * + * @return Returns the job runner type as {@link feast.core.job.Runner} + */ + public feast.core.job.Runner getType() { + return feast.core.job.Runner.fromString(type); + } + } + + @NotNull + /* Population job metric properties */ private MetricsProperties metrics; - private JobUpdatesProperties updates; - } - @Getter - @Setter - public static class JobUpdatesProperties { + /* Timeout in seconds for each attempt to update or submit a new job to the runner */ + @Positive private long jobUpdateTimeoutSeconds; - private long timeoutSeconds; + /* Job update polling interval in millisecond. How frequently Feast will update running jobs. */ + @Positive private long pollingIntervalMilliseconds; } + /** Properties used to configure Feast's managed Kafka feature stream. */ @Getter @Setter public static class StreamProperties { + /* Feature stream type. Only "kafka" is supported. */ + @OneOfStrings({"kafka"}) + @NotBlank private String type; - private Map options; + + /* Feature stream options */ + @NotNull private FeatureStreamOptions options; + + /** Feature stream options */ + @Getter + @Setter + public static class FeatureStreamOptions { + + /* Kafka topic to use for feature sets without source topics. */ + @NotBlank private String topic = "feast-features"; + + /** + * Comma separated list of Kafka bootstrap servers. Used for feature sets without a defined + * source. + */ + @NotBlank private String bootstrapServers = "localhost:9092"; + + /* Defines the number of copies of managed feature stream Kafka. */ + @Positive private short replicationFactor = 1; + + /* Number of Kafka partitions to to use for managed feature stream. */ + @Positive private int partitions = 1; + } } + /** Feast population job metrics */ @Getter @Setter public static class MetricsProperties { + /* Population job metrics enabled */ private boolean enabled; + + /* Metric type. Possible options: statsd */ + @OneOfStrings({"statsd"}) + @NotBlank private String type; + + /* Host of metric sink */ private String host; - private int port; + + /* Port of metric sink */ + @Positive private int port; + } + + /** + * Validates all FeastProperties. This method runs after properties have been initialized and + * individually and conditionally validates each class. + */ + @PostConstruct + public void validate() { + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + Validator validator = factory.getValidator(); + + // Validate root fields in FeastProperties + Set> violations = validator.validate(this); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } + + // Validate Stream properties + Set> streamPropertyViolations = + validator.validate(getStream()); + if (!streamPropertyViolations.isEmpty()) { + throw new ConstraintViolationException(streamPropertyViolations); + } + + // Validate Stream Options + Set> featureStreamOptionsViolations = + validator.validate(getStream().getOptions()); + if (!featureStreamOptionsViolations.isEmpty()) { + throw new ConstraintViolationException(featureStreamOptionsViolations); + } + + // Validate JobProperties + Set> jobPropertiesViolations = validator.validate(getJobs()); + if (!jobPropertiesViolations.isEmpty()) { + throw new ConstraintViolationException(jobPropertiesViolations); + } + + // Validate MetricsProperties + if (getJobs().getMetrics().isEnabled()) { + Set> jobMetricViolations = + validator.validate(getJobs().getMetrics()); + if (!jobMetricViolations.isEmpty()) { + throw new ConstraintViolationException(jobMetricViolations); + } + // Additional custom check for hostname value because there is no built-in Spring annotation + // to validate the value is a DNS resolvable hostname or an IP address. + try { + //noinspection ResultOfMethodCallIgnored + InetAddress.getByName(getJobs().getMetrics().getHost()); + } catch (UnknownHostException e) { + throw new IllegalArgumentException( + "Invalid config value for feast.jobs.metrics.host: " + + getJobs().getMetrics().getHost() + + ". Make sure it is a valid IP address or DNS hostname e.g. localhost or 10.128.10.40. Error detail: " + + e.getMessage()); + } + } } } diff --git a/core/src/main/java/feast/core/config/FeatureStreamConfig.java b/core/src/main/java/feast/core/config/FeatureStreamConfig.java index 45de359ac76..c1982604c31 100644 --- a/core/src/main/java/feast/core/config/FeatureStreamConfig.java +++ b/core/src/main/java/feast/core/config/FeatureStreamConfig.java @@ -17,10 +17,10 @@ package feast.core.config; import com.google.common.base.Strings; -import feast.core.SourceProto.KafkaSourceConfig; -import feast.core.SourceProto.SourceType; import feast.core.config.FeastProperties.StreamProperties; import feast.core.model.Source; +import feast.proto.core.SourceProto.KafkaSourceConfig; +import feast.proto.core.SourceProto.SourceType; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -48,8 +48,8 @@ public Source getDefaultSource(FeastProperties feastProperties) { SourceType featureStreamType = SourceType.valueOf(streamProperties.getType().toUpperCase()); switch (featureStreamType) { case KAFKA: - String bootstrapServers = streamProperties.getOptions().get("bootstrapServers"); - String topicName = streamProperties.getOptions().get("topic"); + String bootstrapServers = streamProperties.getOptions().getBootstrapServers(); + String topicName = streamProperties.getOptions().getTopic(); Map map = new HashMap<>(); map.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); map.put( @@ -59,9 +59,8 @@ public Source getDefaultSource(FeastProperties feastProperties) { NewTopic newTopic = new NewTopic( topicName, - Integer.valueOf(streamProperties.getOptions().getOrDefault("numPartitions", "1")), - Short.valueOf( - streamProperties.getOptions().getOrDefault("replicationFactor", "1"))); + streamProperties.getOptions().getPartitions(), + streamProperties.getOptions().getReplicationFactor()); CreateTopicsResult createTopicsResult = client.createTopics(Collections.singleton(newTopic)); try { diff --git a/core/src/main/java/feast/core/config/JobConfig.java b/core/src/main/java/feast/core/config/JobConfig.java index 728fc0545bf..69636963bea 100644 --- a/core/src/main/java/feast/core/config/JobConfig.java +++ b/core/src/main/java/feast/core/config/JobConfig.java @@ -16,22 +16,11 @@ */ package feast.core.config; -import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; -import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; -import com.google.api.client.json.jackson2.JacksonFactory; -import com.google.api.services.dataflow.Dataflow; -import com.google.api.services.dataflow.DataflowScopes; -import com.google.common.base.Strings; import feast.core.config.FeastProperties.JobProperties; -import feast.core.config.FeastProperties.JobUpdatesProperties; import feast.core.job.JobManager; -import feast.core.job.Runner; import feast.core.job.dataflow.DataflowJobManager; import feast.core.job.direct.DirectJobRegistry; import feast.core.job.direct.DirectRunnerJobManager; -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.util.HashMap; import java.util.Map; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -44,65 +33,26 @@ public class JobConfig { /** - * Get a JobManager according to the runner type and dataflow configuration. + * Get a JobManager according to the runner type and Dataflow configuration. * * @param feastProperties feast config properties */ @Bean @Autowired - public JobManager getJobManager( - FeastProperties feastProperties, DirectJobRegistry directJobRegistry) { + public JobManager getJobManager(FeastProperties feastProperties) { JobProperties jobProperties = feastProperties.getJobs(); - Runner runner = Runner.fromString(jobProperties.getRunner()); - if (jobProperties.getOptions() == null) { - jobProperties.setOptions(new HashMap<>()); - } - Map jobOptions = jobProperties.getOptions(); - switch (runner) { - case DATAFLOW: - if (Strings.isNullOrEmpty(jobOptions.getOrDefault("region", null)) - || Strings.isNullOrEmpty(jobOptions.getOrDefault("project", null))) { - log.error("Project and location of the Dataflow runner is not configured"); - throw new IllegalStateException( - "Project and location of Dataflow runner must be specified for jobs to be run on Dataflow runner."); - } - try { - GoogleCredential credential = - GoogleCredential.getApplicationDefault().createScoped(DataflowScopes.all()); - Dataflow dataflow = - new Dataflow( - GoogleNetHttpTransport.newTrustedTransport(), - JacksonFactory.getDefaultInstance(), - credential); + FeastProperties.JobProperties.Runner runner = jobProperties.getActiveRunner(); + Map runnerConfigOptions = runner.getOptions(); + FeastProperties.MetricsProperties metrics = jobProperties.getMetrics(); - return new DataflowJobManager( - dataflow, jobProperties.getOptions(), jobProperties.getMetrics()); - } catch (IOException e) { - throw new IllegalStateException( - "Unable to find credential required for Dataflow monitoring API", e); - } catch (GeneralSecurityException e) { - throw new IllegalStateException("Security exception while connecting to Dataflow API", e); - } catch (Exception e) { - throw new IllegalStateException("Unable to initialize DataflowJobManager", e); - } + switch (runner.getType()) { + case DATAFLOW: + return new DataflowJobManager(runnerConfigOptions, metrics); case DIRECT: - return new DirectRunnerJobManager( - jobProperties.getOptions(), directJobRegistry, jobProperties.getMetrics()); + return new DirectRunnerJobManager(runnerConfigOptions, new DirectJobRegistry(), metrics); default: - throw new IllegalArgumentException("Unsupported runner: " + jobProperties.getRunner()); + throw new IllegalArgumentException("Unsupported runner: " + runner); } } - - /** Get a direct job registry */ - @Bean - public DirectJobRegistry directJobRegistry() { - return new DirectJobRegistry(); - } - - /** Extracts job update options from feast core options. */ - @Bean - public JobUpdatesProperties jobUpdatesProperties(FeastProperties feastProperties) { - return feastProperties.getJobs().getUpdates(); - } } diff --git a/core/src/main/java/feast/core/dao/FeatureSetRepository.java b/core/src/main/java/feast/core/dao/FeatureSetRepository.java index 3eba2108889..b136650dfdf 100644 --- a/core/src/main/java/feast/core/dao/FeatureSetRepository.java +++ b/core/src/main/java/feast/core/dao/FeatureSetRepository.java @@ -25,25 +25,16 @@ public interface FeatureSetRepository extends JpaRepository long count(); - // Find single feature set by project, name, and version - FeatureSet findFeatureSetByNameAndProject_NameAndVersion( - String name, String project, Integer version); + // Find single feature set by project and name + FeatureSet findFeatureSetByNameAndProject_Name(String name, String project); - // Find single latest version of a feature set by project and name (LIKE) - FeatureSet findFirstFeatureSetByNameLikeAndProject_NameOrderByVersionDesc( - String name, String project); + // find all feature sets and order by name + List findAllByOrderByNameAsc(); - // find all feature sets and order by name and version - List findAllByOrderByNameAscVersionAsc(); + // find all feature sets matching the given name pattern with a specific project. + List findAllByNameLikeAndProject_NameOrderByNameAsc(String name, String project_name); - // find all feature sets within a project and order by name and version - List findAllByProject_NameOrderByNameAscVersionAsc(String project_name); - - // find all versions of feature sets matching the given name pattern with a specific project. - List findAllByNameLikeAndProject_NameOrderByNameAscVersionAsc( - String name, String project_name); - - // find all versions of feature sets matching the given name pattern and project pattern - List findAllByNameLikeAndProject_NameLikeOrderByNameAscVersionAsc( + // find all feature sets matching the given name pattern and project pattern + List findAllByNameLikeAndProject_NameLikeOrderByNameAsc( String name, String project_name); } diff --git a/core/src/main/java/feast/core/dao/JobRepository.java b/core/src/main/java/feast/core/dao/JobRepository.java index 98da76912e7..c61f3eacc05 100644 --- a/core/src/main/java/feast/core/dao/JobRepository.java +++ b/core/src/main/java/feast/core/dao/JobRepository.java @@ -16,6 +16,7 @@ */ package feast.core.dao; +import feast.core.model.FeatureSet; import feast.core.model.Job; import feast.core.model.JobStatus; import java.util.Collection; @@ -29,4 +30,10 @@ public interface JobRepository extends JpaRepository { List findByStatusNotIn(Collection statuses); List findBySourceIdAndStoreNameOrderByLastUpdatedDesc(String sourceId, String storeName); + + // find jobs by feast store name + List findByStoreName(String storeName); + + // find jobs by featureset + List findByFeatureSetsIn(List featureSets); } diff --git a/core/src/main/java/feast/core/grpc/CoreServiceImpl.java b/core/src/main/java/feast/core/grpc/CoreServiceImpl.java index b8d0670d0d2..412d3d31198 100644 --- a/core/src/main/java/feast/core/grpc/CoreServiceImpl.java +++ b/core/src/main/java/feast/core/grpc/CoreServiceImpl.java @@ -16,34 +16,44 @@ */ package feast.core.grpc; -import feast.core.CoreServiceGrpc.CoreServiceImplBase; -import feast.core.CoreServiceProto.ApplyFeatureSetRequest; -import feast.core.CoreServiceProto.ApplyFeatureSetResponse; -import feast.core.CoreServiceProto.ArchiveProjectRequest; -import feast.core.CoreServiceProto.ArchiveProjectResponse; -import feast.core.CoreServiceProto.CreateProjectRequest; -import feast.core.CoreServiceProto.CreateProjectResponse; -import feast.core.CoreServiceProto.GetFeastCoreVersionRequest; -import feast.core.CoreServiceProto.GetFeastCoreVersionResponse; -import feast.core.CoreServiceProto.GetFeatureSetRequest; -import feast.core.CoreServiceProto.GetFeatureSetResponse; -import feast.core.CoreServiceProto.ListFeatureSetsRequest; -import feast.core.CoreServiceProto.ListFeatureSetsResponse; -import feast.core.CoreServiceProto.ListProjectsRequest; -import feast.core.CoreServiceProto.ListProjectsResponse; -import feast.core.CoreServiceProto.ListStoresRequest; -import feast.core.CoreServiceProto.ListStoresResponse; -import feast.core.CoreServiceProto.UpdateStoreRequest; -import feast.core.CoreServiceProto.UpdateStoreResponse; +import com.google.api.gax.rpc.InvalidArgumentException; +import com.google.protobuf.InvalidProtocolBufferException; import feast.core.exception.RetrievalException; import feast.core.grpc.interceptors.MonitoringInterceptor; import feast.core.model.Project; import feast.core.service.AccessManagementService; +import feast.core.service.JobService; import feast.core.service.SpecService; +import feast.proto.core.CoreServiceGrpc.CoreServiceImplBase; +import feast.proto.core.CoreServiceProto.ApplyFeatureSetRequest; +import feast.proto.core.CoreServiceProto.ApplyFeatureSetResponse; +import feast.proto.core.CoreServiceProto.ArchiveProjectRequest; +import feast.proto.core.CoreServiceProto.ArchiveProjectResponse; +import feast.proto.core.CoreServiceProto.CreateProjectRequest; +import feast.proto.core.CoreServiceProto.CreateProjectResponse; +import feast.proto.core.CoreServiceProto.GetFeastCoreVersionRequest; +import feast.proto.core.CoreServiceProto.GetFeastCoreVersionResponse; +import feast.proto.core.CoreServiceProto.GetFeatureSetRequest; +import feast.proto.core.CoreServiceProto.GetFeatureSetResponse; +import feast.proto.core.CoreServiceProto.ListFeatureSetsRequest; +import feast.proto.core.CoreServiceProto.ListFeatureSetsResponse; +import feast.proto.core.CoreServiceProto.ListIngestionJobsRequest; +import feast.proto.core.CoreServiceProto.ListIngestionJobsResponse; +import feast.proto.core.CoreServiceProto.ListProjectsRequest; +import feast.proto.core.CoreServiceProto.ListProjectsResponse; +import feast.proto.core.CoreServiceProto.ListStoresRequest; +import feast.proto.core.CoreServiceProto.ListStoresResponse; +import feast.proto.core.CoreServiceProto.RestartIngestionJobRequest; +import feast.proto.core.CoreServiceProto.RestartIngestionJobResponse; +import feast.proto.core.CoreServiceProto.StopIngestionJobRequest; +import feast.proto.core.CoreServiceProto.StopIngestionJobResponse; +import feast.proto.core.CoreServiceProto.UpdateStoreRequest; +import feast.proto.core.CoreServiceProto.UpdateStoreResponse; import io.grpc.Status; import io.grpc.StatusRuntimeException; import io.grpc.stub.StreamObserver; import java.util.List; +import java.util.NoSuchElementException; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.lognet.springboot.grpc.GRpcService; @@ -56,11 +66,16 @@ public class CoreServiceImpl extends CoreServiceImplBase { private SpecService specService; private AccessManagementService accessManagementService; + private JobService jobService; @Autowired - public CoreServiceImpl(SpecService specService, AccessManagementService accessManagementService) { + public CoreServiceImpl( + SpecService specService, + AccessManagementService accessManagementService, + JobService jobService) { this.specService = specService; this.accessManagementService = accessManagementService; + this.jobService = jobService; } @Override @@ -77,7 +92,7 @@ public void getFeatureSet( GetFeatureSetResponse response = specService.getFeatureSet(request); responseObserver.onNext(response); responseObserver.onCompleted(); - } catch (RetrievalException | StatusRuntimeException e) { + } catch (RetrievalException | StatusRuntimeException | InvalidProtocolBufferException e) { log.error("Exception has occurred in GetFeatureSet method: ", e); responseObserver.onError( Status.INTERNAL.withDescription(e.getMessage()).withCause(e).asRuntimeException()); @@ -91,7 +106,7 @@ public void listFeatureSets( ListFeatureSetsResponse response = specService.listFeatureSets(request.getFilter()); responseObserver.onNext(response); responseObserver.onCompleted(); - } catch (RetrievalException | IllegalArgumentException e) { + } catch (RetrievalException | IllegalArgumentException | InvalidProtocolBufferException e) { log.error("Exception has occurred in ListFeatureSet method: ", e); responseObserver.onError( Status.INTERNAL.withDescription(e.getMessage()).withCause(e).asRuntimeException()); @@ -168,6 +183,17 @@ public void archiveProject( accessManagementService.archiveProject(request.getName()); responseObserver.onNext(ArchiveProjectResponse.getDefaultInstance()); responseObserver.onCompleted(); + } catch (IllegalArgumentException e) { + log.error("Recieved an invalid request on calling archiveProject method:", e); + responseObserver.onError( + Status.INVALID_ARGUMENT + .withDescription(e.getMessage()) + .withCause(e) + .asRuntimeException()); + } catch (UnsupportedOperationException e) { + log.error("Attempted to archive an unsupported project:", e); + responseObserver.onError( + Status.UNIMPLEMENTED.withDescription(e.getMessage()).withCause(e).asRuntimeException()); } catch (Exception e) { log.error("Exception has occurred in the createProject method: ", e); responseObserver.onError( @@ -191,4 +217,69 @@ public void listProjects( Status.INTERNAL.withDescription(e.getMessage()).withCause(e).asRuntimeException()); } } + + @Override + public void listIngestionJobs( + ListIngestionJobsRequest request, + StreamObserver responseObserver) { + try { + ListIngestionJobsResponse response = this.jobService.listJobs(request); + responseObserver.onNext(response); + responseObserver.onCompleted(); + } catch (InvalidArgumentException e) { + log.error("Recieved an invalid request on calling listIngestionJobs method:", e); + responseObserver.onError( + Status.INVALID_ARGUMENT.withDescription(e.getMessage()).withCause(e).asException()); + } catch (Exception e) { + log.error("Unexpected exception on calling listIngestionJobs method:", e); + responseObserver.onError( + Status.INTERNAL.withDescription(e.getMessage()).withCause(e).asRuntimeException()); + } + } + + @Override + public void restartIngestionJob( + RestartIngestionJobRequest request, + StreamObserver responseObserver) { + try { + RestartIngestionJobResponse response = this.jobService.restartJob(request); + responseObserver.onNext(response); + responseObserver.onCompleted(); + } catch (NoSuchElementException e) { + log.error( + "Attempted to restart an nonexistent job on calling restartIngestionJob method:", e); + responseObserver.onError( + Status.NOT_FOUND.withDescription(e.getMessage()).withCause(e).asException()); + } catch (UnsupportedOperationException e) { + log.error("Recieved an unsupported request on calling restartIngestionJob method:", e); + responseObserver.onError( + Status.FAILED_PRECONDITION.withDescription(e.getMessage()).withCause(e).asException()); + } catch (Exception e) { + log.error("Unexpected exception on calling restartIngestionJob method:", e); + responseObserver.onError( + Status.INTERNAL.withDescription(e.getMessage()).withCause(e).asRuntimeException()); + } + } + + @Override + public void stopIngestionJob( + StopIngestionJobRequest request, StreamObserver responseObserver) { + try { + StopIngestionJobResponse response = this.jobService.stopJob(request); + responseObserver.onNext(response); + responseObserver.onCompleted(); + } catch (NoSuchElementException e) { + log.error("Attempted to stop an nonexistent job on calling stopIngestionJob method:", e); + responseObserver.onError( + Status.NOT_FOUND.withDescription(e.getMessage()).withCause(e).asException()); + } catch (UnsupportedOperationException e) { + log.error("Recieved an unsupported request on calling stopIngestionJob method:", e); + responseObserver.onError( + Status.FAILED_PRECONDITION.withDescription(e.getMessage()).withCause(e).asException()); + } catch (Exception e) { + log.error("Unexpected exception on calling stopIngestionJob method:", e); + responseObserver.onError( + Status.INTERNAL.withDescription(e.getMessage()).withCause(e).asRuntimeException()); + } + } } diff --git a/core/src/main/java/feast/core/grpc/HealthServiceImpl.java b/core/src/main/java/feast/core/grpc/HealthServiceImpl.java new file mode 100644 index 00000000000..3bd2f8748fe --- /dev/null +++ b/core/src/main/java/feast/core/grpc/HealthServiceImpl.java @@ -0,0 +1,54 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.core.grpc; + +import feast.core.service.AccessManagementService; +import io.grpc.Status; +import io.grpc.health.v1.HealthGrpc.HealthImplBase; +import io.grpc.health.v1.HealthProto.HealthCheckRequest; +import io.grpc.health.v1.HealthProto.HealthCheckResponse; +import io.grpc.health.v1.HealthProto.HealthCheckResponse.ServingStatus; +import io.grpc.stub.StreamObserver; +import lombok.extern.slf4j.Slf4j; +import org.lognet.springboot.grpc.GRpcService; +import org.springframework.beans.factory.annotation.Autowired; + +@Slf4j +@GRpcService +public class HealthServiceImpl extends HealthImplBase { + private final AccessManagementService accessManagementService; + + @Autowired + public HealthServiceImpl(AccessManagementService accessManagementService) { + this.accessManagementService = accessManagementService; + } + + @Override + public void check( + HealthCheckRequest request, StreamObserver responseObserver) { + try { + accessManagementService.listProjects(); + responseObserver.onNext( + HealthCheckResponse.newBuilder().setStatus(ServingStatus.SERVING).build()); + responseObserver.onCompleted(); + } catch (Exception e) { + log.error("Health Check: unable to retrieve projects.\nError: %s", e); + responseObserver.onError( + Status.INTERNAL.withDescription(e.getMessage()).withCause(e).asRuntimeException()); + } + } +} diff --git a/core/src/main/java/feast/core/http/HealthController.java b/core/src/main/java/feast/core/http/HealthController.java deleted file mode 100644 index 2451ed793ed..00000000000 --- a/core/src/main/java/feast/core/http/HealthController.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright 2018-2019 The Feast Authors - * - * 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 feast.core.http; - -import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; - -import java.sql.Connection; -import java.sql.SQLException; -import javax.sql.DataSource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -/** Web http for pod health-check endpoints. */ -@Slf4j -@RestController -public class HealthController { - - private final DataSource db; - - @Autowired - public HealthController(DataSource datasource) { - this.db = datasource; - } - - /** - * /ping endpoint checks if the application is ready to serve traffic by checking if it is able to - * access the metadata db. - */ - @RequestMapping(value = "/ping", method = RequestMethod.GET) - public ResponseEntity ping() { - return ResponseEntity.ok("pong"); - } - - /** - * /healthz endpoint checks if the application is healthy by checking if the application still has - * access to the metadata db. - */ - @RequestMapping(value = "/healthz", method = RequestMethod.GET) - public ResponseEntity healthz() { - try (Connection conn = db.getConnection()) { - if (conn.isValid(10)) { - return ResponseEntity.ok("healthy"); - } - log.error("Unable to reach DB"); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body("Unable to establish connection with DB"); - } catch (SQLException e) { - log.error("Unable to reach DB: {}", e); - return ResponseEntity.status(INTERNAL_SERVER_ERROR).body(e.getMessage()); - } - } -} diff --git a/core/src/main/java/feast/core/job/JobManager.java b/core/src/main/java/feast/core/job/JobManager.java index 99880cdb764..eda211b7574 100644 --- a/core/src/main/java/feast/core/job/JobManager.java +++ b/core/src/main/java/feast/core/job/JobManager.java @@ -51,6 +51,16 @@ public interface JobManager { */ void abortJob(String extId); + /** + * Restart an job. If job is an terminated state, will simply start the job. Might cause data to + * be lost during when restarting running jobs in some implementations. Refer to on docs the + * specific implementation. + * + * @param job job to restart + * @return the restarted job + */ + Job restartJob(Job job); + /** * Get status of a job given runner-specific job ID. * diff --git a/core/src/main/java/feast/core/job/JobUpdateTask.java b/core/src/main/java/feast/core/job/JobUpdateTask.java index 57b2dfee4f3..056da34b3aa 100644 --- a/core/src/main/java/feast/core/job/JobUpdateTask.java +++ b/core/src/main/java/feast/core/job/JobUpdateTask.java @@ -16,9 +16,7 @@ */ package feast.core.job; -import feast.core.FeatureSetProto; -import feast.core.SourceProto; -import feast.core.StoreProto; +import com.google.common.collect.Sets; import feast.core.log.Action; import feast.core.log.AuditLogger; import feast.core.log.Resource; @@ -27,10 +25,10 @@ import feast.core.model.JobStatus; import feast.core.model.Source; import feast.core.model.Store; +import feast.proto.core.FeatureSetProto.FeatureSetStatus; import java.time.Instant; import java.util.List; import java.util.Optional; -import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -38,7 +36,6 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import java.util.stream.Collectors; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -52,133 +49,101 @@ @Getter public class JobUpdateTask implements Callable { - private final List featureSets; - private final SourceProto.Source sourceSpec; - private final StoreProto.Store store; + private final List featureSets; + private final Source source; + private final Store store; private final Optional currentJob; - private JobManager jobManager; - private long jobUpdateTimeoutSeconds; + private final JobManager jobManager; + private final long jobUpdateTimeoutSeconds; + private final String runnerName; public JobUpdateTask( - List featureSets, - SourceProto.Source sourceSpec, - StoreProto.Store store, + List featureSets, + Source source, + Store store, Optional currentJob, JobManager jobManager, long jobUpdateTimeoutSeconds) { this.featureSets = featureSets; - this.sourceSpec = sourceSpec; + this.source = source; this.store = store; this.currentJob = currentJob; this.jobManager = jobManager; this.jobUpdateTimeoutSeconds = jobUpdateTimeoutSeconds; + this.runnerName = jobManager.getRunnerType().toString(); } @Override public Job call() { ExecutorService executorService = Executors.newSingleThreadExecutor(); - Source source = Source.fromProto(sourceSpec); Future submittedJob; - if (currentJob.isPresent()) { - Set existingFeatureSetsPopulatedByJob = - currentJob.get().getFeatureSets().stream() - .map(FeatureSet::getId) - .collect(Collectors.toSet()); - Set newFeatureSetsPopulatedByJob = - featureSets.stream() - .map(fs -> FeatureSet.fromProto(fs).getId()) - .collect(Collectors.toSet()); - if (existingFeatureSetsPopulatedByJob.size() == newFeatureSetsPopulatedByJob.size() - && existingFeatureSetsPopulatedByJob.containsAll(newFeatureSetsPopulatedByJob)) { - Job job = currentJob.get(); - JobStatus newJobStatus = jobManager.getJobStatus(job); - if (newJobStatus != job.getStatus()) { - AuditLogger.log( - Resource.JOB, - job.getId(), - Action.STATUS_CHANGE, - "Job status updated: changed from %s to %s", - job.getStatus(), - newJobStatus); - } - job.setStatus(newJobStatus); - return job; + + if (currentJob.isEmpty()) { + submittedJob = executorService.submit(this::createJob); + } else { + Job job = currentJob.get(); + + if (requiresUpdate(job)) { + submittedJob = executorService.submit(() -> updateJob(job)); } else { - submittedJob = - executorService.submit(() -> updateJob(currentJob.get(), featureSets, store)); + return updateStatus(job); } - } else { - String jobId = createJobId(source.getId(), store.getName()); - submittedJob = executorService.submit(() -> startJob(jobId, featureSets, sourceSpec, store)); } - Job job = null; try { - job = submittedJob.get(getJobUpdateTimeoutSeconds(), TimeUnit.SECONDS); + return submittedJob.get(getJobUpdateTimeoutSeconds(), TimeUnit.SECONDS); } catch (InterruptedException | ExecutionException | TimeoutException e) { log.warn("Unable to start job for source {} and sink {}: {}", source, store, e.getMessage()); + return null; + } finally { executorService.shutdownNow(); } - return job; + } + + boolean requiresUpdate(Job job) { + // If set of feature sets has changed + if (!Sets.newHashSet(featureSets).equals(Sets.newHashSet(job.getFeatureSets()))) { + return true; + } + // If any of the incoming feature sets were updated + for (FeatureSet featureSet : featureSets) { + if (featureSet.getStatus() == FeatureSetStatus.STATUS_PENDING) { + return true; + } + } + return false; + } + + private Job createJob() { + String jobId = createJobId(source.getId(), store.getName()); + return startJob(jobId); } /** Start or update the job to ingest data to the sink. */ - private Job startJob( - String jobId, - List featureSetProtos, - SourceProto.Source source, - StoreProto.Store sinkSpec) { - - List featureSets = - featureSetProtos.stream() - .map( - fsp -> - FeatureSet.fromProto( - FeatureSetProto.FeatureSet.newBuilder() - .setSpec(fsp.getSpec()) - .setMeta(fsp.getMeta()) - .build())) - .collect(Collectors.toList()); + private Job startJob(String jobId) { + Job job = new Job( - jobId, - "", - jobManager.getRunnerType().toString(), - Source.fromProto(source), - Store.fromProto(sinkSpec), - featureSets, - JobStatus.PENDING); + jobId, "", jobManager.getRunnerType(), source, store, featureSets, JobStatus.PENDING); try { - AuditLogger.log( - Resource.JOB, - jobId, - Action.SUBMIT, - "Building graph and submitting to %s", - jobManager.getRunnerType().getName()); + logAudit(Action.SUBMIT, job, "Building graph and submitting to %s", runnerName); job = jobManager.startJob(job); - if (job.getExtId().isEmpty()) { + var extId = job.getExtId(); + if (extId.isEmpty()) { throw new RuntimeException( String.format("Could not submit job: \n%s", "unable to retrieve job external id")); } - AuditLogger.log( - Resource.JOB, - jobId, - Action.STATUS_CHANGE, - "Job submitted to runner %s with ext id %s.", - jobManager.getRunnerType().getName(), - job.getExtId()); + var auditMessage = "Job submitted to runner %s with ext id %s."; + logAudit(Action.STATUS_CHANGE, job, auditMessage, runnerName, extId); return job; } catch (Exception e) { - AuditLogger.log( - Resource.JOB, - jobId, - Action.STATUS_CHANGE, - "Job failed to be submitted to runner %s. Job status changed to ERROR.", - jobManager.getRunnerType().getName()); + log.error(e.getMessage()); + var auditMessage = "Job failed to be submitted to runner %s. Job status changed to ERROR."; + logAudit(Action.STATUS_CHANGE, job, auditMessage, runnerName); job.setStatus(JobStatus.ERROR); return job; @@ -186,33 +151,33 @@ private Job startJob( } /** Update the given job */ - private Job updateJob( - Job job, List featureSets, StoreProto.Store store) { - job.setFeatureSets( - featureSets.stream() - .map( - fs -> - FeatureSet.fromProto( - FeatureSetProto.FeatureSet.newBuilder() - .setSpec(fs.getSpec()) - .setMeta(fs.getMeta()) - .build())) - .collect(Collectors.toList())); - job.setStore(feast.core.model.Store.fromProto(store)); - AuditLogger.log( - Resource.JOB, - job.getId(), - Action.UPDATE, - "Updating job %s for runner %s", - job.getId(), - jobManager.getRunnerType().getName()); + private Job updateJob(Job job) { + job.setFeatureSets(featureSets); + job.setStore(store); + logAudit(Action.UPDATE, job, "Updating job %s for runner %s", job.getId(), runnerName); return jobManager.updateJob(job); } + private Job updateStatus(Job job) { + JobStatus currentStatus = job.getStatus(); + JobStatus newStatus = jobManager.getJobStatus(job); + if (newStatus != currentStatus) { + var auditMessage = "Job status updated: changed from %s to %s"; + logAudit(Action.STATUS_CHANGE, job, auditMessage, currentStatus, newStatus); + } + + job.setStatus(newStatus); + return job; + } + String createJobId(String sourceId, String storeName) { String dateSuffix = String.valueOf(Instant.now().toEpochMilli()); String sourceIdTrunc = sourceId.split("/")[0].toLowerCase(); String jobId = String.format("%s-to-%s", sourceIdTrunc, storeName) + dateSuffix; return jobId.replaceAll("_", "-"); } + + private void logAudit(Action action, Job job, String detail, Object... args) { + AuditLogger.log(Resource.JOB, job.getId(), action, detail, args); + } } diff --git a/core/src/main/java/feast/core/job/Runner.java b/core/src/main/java/feast/core/job/Runner.java index 637621be359..acccb70c8b2 100644 --- a/core/src/main/java/feast/core/job/Runner.java +++ b/core/src/main/java/feast/core/job/Runner.java @@ -16,27 +16,37 @@ */ package feast.core.job; +import java.util.NoSuchElementException; + +/** + * An Apache Beam Runner, for which Feast Core supports managing ingestion jobs. + * + * @see Beam Runners + */ public enum Runner { DATAFLOW("DataflowRunner"), FLINK("FlinkRunner"), DIRECT("DirectRunner"); - private final String name; + private final String humanName; - Runner(String name) { - this.name = name; + Runner(String humanName) { + this.humanName = humanName; } - public String getName() { - return name; + /** Returns the human readable name of this runner, usable in logging, config files, etc. */ + @Override + public String toString() { + return humanName; } - public static Runner fromString(String runner) { + /** Parses a runner from its human readable name. */ + public static Runner fromString(String humanName) { for (Runner r : Runner.values()) { - if (r.getName().equals(runner)) { + if (r.toString().equals(humanName)) { return r; } } - throw new IllegalArgumentException("Unknown value: " + runner); + throw new NoSuchElementException("Unknown Runner value: " + humanName); } } diff --git a/core/src/main/java/feast/core/job/dataflow/DataflowJobManager.java b/core/src/main/java/feast/core/job/dataflow/DataflowJobManager.java index 2de46ae1f2d..7b9df0abd52 100644 --- a/core/src/main/java/feast/core/job/dataflow/DataflowJobManager.java +++ b/core/src/main/java/feast/core/job/dataflow/DataflowJobManager.java @@ -18,33 +18,35 @@ import static feast.core.util.PipelineUtil.detectClassPathResourcesToStage; +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; +import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; +import com.google.api.client.json.jackson2.JacksonFactory; import com.google.api.services.dataflow.Dataflow; +import com.google.api.services.dataflow.DataflowScopes; import com.google.common.base.Strings; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.util.JsonFormat; -import com.google.protobuf.util.JsonFormat.Printer; -import feast.core.FeatureSetProto; -import feast.core.SourceProto; -import feast.core.StoreProto; import feast.core.config.FeastProperties.MetricsProperties; import feast.core.exception.JobExecutionException; import feast.core.job.JobManager; import feast.core.job.Runner; -import feast.core.model.FeatureSet; -import feast.core.model.Job; -import feast.core.model.JobStatus; -import feast.core.model.Project; -import feast.core.model.Source; -import feast.core.model.Store; +import feast.core.job.option.FeatureSetJsonByteConverter; +import feast.core.model.*; import feast.core.util.TypeConversion; import feast.ingestion.ImportJob; +import feast.ingestion.options.BZip2Compressor; import feast.ingestion.options.ImportOptions; +import feast.ingestion.options.OptionCompressor; +import feast.proto.core.FeatureSetProto; +import feast.proto.core.SourceProto; +import feast.proto.core.StoreProto; import java.io.IOException; +import java.security.GeneralSecurityException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.apache.beam.runners.dataflow.DataflowPipelineJob; import org.apache.beam.runners.dataflow.DataflowRunner; @@ -63,12 +65,46 @@ public class DataflowJobManager implements JobManager { private final MetricsProperties metrics; public DataflowJobManager( - Dataflow dataflow, Map defaultOptions, MetricsProperties metricsProperties) { - this.defaultOptions = defaultOptions; + Map runnerConfigOptions, MetricsProperties metricsProperties) { + this(runnerConfigOptions, metricsProperties, getGoogleCredential()); + } + + public DataflowJobManager( + Map runnerConfigOptions, + MetricsProperties metricsProperties, + Credential credential) { + + DataflowRunnerConfig config = new DataflowRunnerConfig(runnerConfigOptions); + + Dataflow dataflow = null; + try { + dataflow = + new Dataflow( + GoogleNetHttpTransport.newTrustedTransport(), + JacksonFactory.getDefaultInstance(), + credential); + } catch (GeneralSecurityException e) { + throw new IllegalStateException("Security exception while connecting to Dataflow API", e); + } catch (IOException e) { + throw new IllegalStateException("Unable to initialize DataflowJobManager", e); + } + + this.defaultOptions = runnerConfigOptions; this.dataflow = dataflow; this.metrics = metricsProperties; - this.projectId = defaultOptions.get("project"); - this.location = defaultOptions.get("region"); + this.projectId = config.getProject(); + this.location = config.getRegion(); + } + + private static Credential getGoogleCredential() { + GoogleCredential credential = null; + try { + credential = GoogleCredential.getApplicationDefault().createScoped(DataflowScopes.all()); + } catch (IOException e) { + throw new IllegalStateException( + "Unable to find credential required for Dataflow monitoring API", e); + } + return credential; } @Override @@ -78,17 +114,28 @@ public Runner getRunnerType() { @Override public Job startJob(Job job) { - List featureSetProtos = - job.getFeatureSets().stream().map(FeatureSet::toProto).collect(Collectors.toList()); try { - return submitDataflowJob( - job.getId(), - featureSetProtos, - job.getSource().toProto(), - job.getStore().toProto(), - false); + List featureSetProtos = new ArrayList<>(); + for (FeatureSet featureSet : job.getFeatureSets()) { + featureSetProtos.add(featureSet.toProto()); + } + String extId = + submitDataflowJob( + job.getId(), + featureSetProtos, + job.getSource().toProto(), + job.getStore().toProto(), + false); + job.setExtId(extId); + return job; + } catch (InvalidProtocolBufferException e) { - throw new RuntimeException(String.format("Unable to start job %s", job.getId()), e); + log.error(e.getMessage()); + throw new IllegalArgumentException( + String.format( + "DataflowJobManager failed to START job with id '%s' because the job" + + "has an invalid spec. Please check the FeatureSet, Source and Store specs. Actual error message: %s", + job.getId(), e.getMessage())); } } @@ -101,14 +148,29 @@ public Job startJob(Job job) { @Override public Job updateJob(Job job) { try { - List featureSetProtos = - job.getFeatureSets().stream().map(FeatureSet::toProto).collect(Collectors.toList()); + List featureSetProtos = new ArrayList<>(); + for (FeatureSet featureSet : job.getFeatureSets()) { + featureSetProtos.add(featureSet.toProto()); + } - return submitDataflowJob( - job.getId(), featureSetProtos, job.getSource().toProto(), job.getStore().toProto(), true); + String extId = + submitDataflowJob( + job.getId(), + featureSetProtos, + job.getSource().toProto(), + job.getStore().toProto(), + true); + job.setExtId(extId); + job.setStatus(JobStatus.PENDING); + return job; } catch (InvalidProtocolBufferException e) { - throw new RuntimeException(String.format("Unable to update job %s", job.getId()), e); + log.error(e.getMessage()); + throw new IllegalArgumentException( + String.format( + "DataflowJobManager failed to UPDATE job with id '%s' because the job" + + "has an invalid spec. Please check the FeatureSet, Source and Store specs. Actual error message: %s", + job.getId(), e.getMessage())); } } @@ -142,6 +204,25 @@ public void abortJob(String dataflowJobId) { } } + /** + * Restart a Dataflow job. Dataflow should ensure continuity such that no data should be lost + * during the restart operation. + * + * @param job job to restart + * @return the restarted job + */ + @Override + public Job restartJob(Job job) { + if (job.getStatus().isTerminal()) { + // job yet not running: just start job + return this.startJob(job); + } else { + // job is running - updating the job without changing the job has + // the effect of restarting the job + return this.updateJob(job); + } + } + /** * Get status of a dataflow job with given id and try to map it into Feast's JobStatus. * @@ -150,7 +231,7 @@ public void abortJob(String dataflowJobId) { */ @Override public JobStatus getJobStatus(Job job) { - if (!Runner.DATAFLOW.getName().equals(job.getRunner())) { + if (job.getRunner() != RUNNER_TYPE) { return job.getStatus(); } @@ -167,7 +248,7 @@ public JobStatus getJobStatus(Job job) { return JobStatus.UNKNOWN; } - private Job submitDataflowJob( + private String submitDataflowJob( String jobName, List featureSetProtos, SourceProto.Source source, @@ -176,26 +257,8 @@ private Job submitDataflowJob( try { ImportOptions pipelineOptions = getPipelineOptions(jobName, featureSetProtos, sink, update); DataflowPipelineJob pipelineResult = runPipeline(pipelineOptions); - List featureSets = - featureSetProtos.stream() - .map( - fsp -> { - FeatureSet featureSet = new FeatureSet(); - featureSet.setName(fsp.getSpec().getName()); - featureSet.setVersion(fsp.getSpec().getVersion()); - featureSet.setProject(new Project(fsp.getSpec().getProject())); - return featureSet; - }) - .collect(Collectors.toList()); String jobId = waitForJobToRun(pipelineResult); - return new Job( - jobName, - jobId, - getRunnerType().getName(), - Source.fromProto(source), - Store.fromProto(sink), - featureSets, - JobStatus.PENDING); + return jobId; } catch (Exception e) { log.error("Error submitting job", e); throw new JobExecutionException(String.format("Error running ingestion job: %s", e), e); @@ -210,14 +273,14 @@ private ImportOptions getPipelineOptions( throws IOException { String[] args = TypeConversion.convertMapToArgs(defaultOptions); ImportOptions pipelineOptions = PipelineOptionsFactory.fromArgs(args).as(ImportOptions.class); - Printer printer = JsonFormat.printer(); - List featureSetsJson = new ArrayList<>(); - for (FeatureSetProto.FeatureSet featureSet : featureSets) { - featureSetsJson.add(printer.print(featureSet.getSpec())); - } - pipelineOptions.setFeatureSetJson(featureSetsJson); - pipelineOptions.setStoreJson(Collections.singletonList(printer.print(sink))); + + OptionCompressor> featureSetJsonCompressor = + new BZip2Compressor<>(new FeatureSetJsonByteConverter()); + + pipelineOptions.setFeatureSetJson(featureSetJsonCompressor.compress(featureSets)); + pipelineOptions.setStoreJson(Collections.singletonList(JsonFormat.printer().print(sink))); pipelineOptions.setProject(projectId); + pipelineOptions.setDefaultFeastProject(Project.DEFAULT_NAME); pipelineOptions.setUpdate(update); pipelineOptions.setRunner(DataflowRunner.class); pipelineOptions.setJobName(jobName); diff --git a/core/src/main/java/feast/core/job/dataflow/DataflowRunnerConfig.java b/core/src/main/java/feast/core/job/dataflow/DataflowRunnerConfig.java new file mode 100644 index 00000000000..6fe93ca80cd --- /dev/null +++ b/core/src/main/java/feast/core/job/dataflow/DataflowRunnerConfig.java @@ -0,0 +1,113 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2019 The Feast Authors + * + * 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 feast.core.job.dataflow; + +import java.lang.reflect.Field; +import java.util.Map; +import java.util.Set; +import javax.validation.*; +import javax.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + +/** DataflowRunnerConfig contains configuration fields for the Dataflow job runner. */ +@Getter +@Setter +public class DataflowRunnerConfig { + + public DataflowRunnerConfig(Map runnerConfigOptions) { + + // Try to find all fields in DataflowRunnerConfig inside the runnerConfigOptions and map it into + // this object + for (Field field : DataflowRunnerConfig.class.getFields()) { + String fieldName = field.getName(); + try { + if (!runnerConfigOptions.containsKey(fieldName)) { + continue; + } + String value = runnerConfigOptions.get(fieldName); + + if (Boolean.class.equals(field.getType())) { + field.set(this, Boolean.valueOf(value)); + continue; + } + if (field.getType() == Integer.class) { + field.set(this, Integer.valueOf(value)); + continue; + } + field.set(this, value); + } catch (IllegalAccessException e) { + throw new RuntimeException( + String.format( + "Could not successfully convert DataflowRunnerConfig for key: %s", fieldName), + e); + } + } + validate(); + } + + /* Project id to use when launching jobs. */ + @NotBlank public String project; + + /* The Google Compute Engine region for creating Dataflow jobs. */ + @NotBlank public String region; + + /* GCP availability zone for operations. */ + @NotBlank public String zone; + + /* Run the job as a specific service account, instead of the default GCE robot. */ + public String serviceAccount; + + /* GCE network for launching workers. */ + @NotBlank public String network; + + /* GCE subnetwork for launching workers. */ + @NotBlank public String subnetwork; + + /* Machine type to create Dataflow worker VMs as. */ + public String workerMachineType; + + /* The autoscaling algorithm to use for the workerpool. */ + public String autoscalingAlgorithm; + + /* Specifies whether worker pools should be started with public IP addresses. */ + public Boolean usePublicIps; + + /** + * A pipeline level default location for storing temporary files. Support Google Cloud Storage + * locations, e.g. gs://bucket/object + */ + @NotBlank public String tempLocation; + + /* The maximum number of workers to use for the workerpool. */ + public Integer maxNumWorkers; + + /* BigQuery table specification, e.g. PROJECT_ID:DATASET_ID.PROJECT_ID */ + public String deadLetterTableSpec; + + /** Validates Dataflow runner configuration options */ + public void validate() { + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + Validator validator = factory.getValidator(); + + Set> dataflowRunnerConfigViolation = + validator.validate(this); + if (!dataflowRunnerConfigViolation.isEmpty()) { + throw new ConstraintViolationException(dataflowRunnerConfigViolation); + } + } +} diff --git a/core/src/main/java/feast/core/job/direct/DirectRunnerJobManager.java b/core/src/main/java/feast/core/job/direct/DirectRunnerJobManager.java index fdf3aad9bc3..2e2b43047ea 100644 --- a/core/src/main/java/feast/core/job/direct/DirectRunnerJobManager.java +++ b/core/src/main/java/feast/core/job/direct/DirectRunnerJobManager.java @@ -17,28 +17,28 @@ package feast.core.job.direct; import com.google.common.base.Strings; -import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.util.JsonFormat; -import com.google.protobuf.util.JsonFormat.Printer; -import feast.core.FeatureSetProto; -import feast.core.FeatureSetProto.FeatureSetSpec; -import feast.core.StoreProto; import feast.core.config.FeastProperties.MetricsProperties; import feast.core.exception.JobExecutionException; import feast.core.job.JobManager; import feast.core.job.Runner; +import feast.core.job.option.FeatureSetJsonByteConverter; import feast.core.model.FeatureSet; import feast.core.model.Job; import feast.core.model.JobStatus; +import feast.core.model.Project; import feast.core.util.TypeConversion; import feast.ingestion.ImportJob; +import feast.ingestion.options.BZip2Compressor; import feast.ingestion.options.ImportOptions; +import feast.ingestion.options.OptionCompressor; +import feast.proto.core.FeatureSetProto; +import feast.proto.core.StoreProto; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.apache.beam.runners.direct.DirectRunner; import org.apache.beam.sdk.PipelineResult; @@ -75,10 +75,12 @@ public Runner getRunnerType() { @Override public Job startJob(Job job) { try { - List featureSetProtos = - job.getFeatureSets().stream().map(FeatureSet::toProto).collect(Collectors.toList()); + List featureSetProtos = new ArrayList<>(); + for (FeatureSet featureSet : job.getFeatureSets()) { + featureSetProtos.add(featureSet.toProto()); + } ImportOptions pipelineOptions = - getPipelineOptions(featureSetProtos, job.getStore().toProto()); + getPipelineOptions(job.getId(), featureSetProtos, job.getStore().toProto()); PipelineResult pipelineResult = runPipeline(pipelineOptions); DirectJob directJob = new DirectJob(job.getId(), pipelineResult); jobs.add(directJob); @@ -92,18 +94,19 @@ public Job startJob(Job job) { } private ImportOptions getPipelineOptions( - List featureSets, StoreProto.Store sink) - throws InvalidProtocolBufferException { + String jobName, List featureSets, StoreProto.Store sink) + throws IOException { String[] args = TypeConversion.convertMapToArgs(defaultOptions); ImportOptions pipelineOptions = PipelineOptionsFactory.fromArgs(args).as(ImportOptions.class); - Printer printer = JsonFormat.printer(); - List featureSetsJson = new ArrayList<>(); - for (FeatureSetProto.FeatureSet featureSet : featureSets) { - featureSetsJson.add(printer.print(featureSet.getSpec())); - } - pipelineOptions.setFeatureSetJson(featureSetsJson); - pipelineOptions.setStoreJson(Collections.singletonList(printer.print(sink))); + + OptionCompressor> featureSetJsonCompressor = + new BZip2Compressor<>(new FeatureSetJsonByteConverter()); + + pipelineOptions.setFeatureSetJson(featureSetJsonCompressor.compress(featureSets)); + pipelineOptions.setJobName(jobName); + pipelineOptions.setStoreJson(Collections.singletonList(JsonFormat.printer().print(sink))); pipelineOptions.setRunner(DirectRunner.class); + pipelineOptions.setDefaultFeastProject(Project.DEFAULT_NAME); pipelineOptions.setProject(""); // set to default value to satisfy validation if (metrics.isEnabled()) { pipelineOptions.setMetricsExporterType(metrics.getType()); @@ -131,10 +134,6 @@ public Job updateJob(Job job) { String jobId = job.getExtId(); abortJob(jobId); try { - List featureSetSpecs = new ArrayList<>(); - for (FeatureSet featureSet : job.getFeatureSets()) { - featureSetSpecs.add(featureSet.toProto().getSpec()); - } return startJob(job); } catch (JobExecutionException e) { throw new JobExecutionException(String.format("Error running ingestion job: %s", e), e); @@ -162,6 +161,25 @@ public PipelineResult runPipeline(ImportOptions pipelineOptions) throws IOExcept return ImportJob.runPipeline(pipelineOptions); } + /** + * Restart a direct runner job. Note that some data will be temporarily lost during when + * restarting running direct runner jobs. See {#link {@link #updateJob(Job)} for more info. + * + * @param job job to restart + * @return the restarted job + */ + @Override + public Job restartJob(Job job) { + if (job.getStatus().isTerminal()) { + // job yet not running: just start job + return this.startJob(job); + } else { + // job is running - updating the job without changing the job has + // the effect of restarting the job. + return this.updateJob(job); + } + } + /** * Gets the state of the direct runner job. Direct runner jobs only have 2 states: RUNNING and * ABORTED. diff --git a/core/src/main/java/feast/core/job/option/FeatureSetJsonByteConverter.java b/core/src/main/java/feast/core/job/option/FeatureSetJsonByteConverter.java new file mode 100644 index 00000000000..2f6b37df1b5 --- /dev/null +++ b/core/src/main/java/feast/core/job/option/FeatureSetJsonByteConverter.java @@ -0,0 +1,47 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.core.job.option; + +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.util.JsonFormat; +import feast.ingestion.options.OptionByteConverter; +import feast.proto.core.FeatureSetProto; +import java.util.ArrayList; +import java.util.List; + +public class FeatureSetJsonByteConverter + implements OptionByteConverter> { + + /** + * Convert list of feature sets to json strings joined by new line, represented as byte arrays + * + * @param featureSets List of feature set protobufs + * @return Byte array representation of the json strings + * @throws InvalidProtocolBufferException + */ + @Override + public byte[] toByte(List featureSets) + throws InvalidProtocolBufferException { + JsonFormat.Printer printer = + JsonFormat.printer().omittingInsignificantWhitespace().printingEnumsAsInts(); + List featureSetsJson = new ArrayList<>(); + for (FeatureSetProto.FeatureSet featureSet : featureSets) { + featureSetsJson.add(printer.print(featureSet.getSpec())); + } + return String.join("\n", featureSetsJson).getBytes(); + } +} diff --git a/core/src/main/java/feast/core/model/Entity.java b/core/src/main/java/feast/core/model/Entity.java new file mode 100644 index 00000000000..6133d492fcc --- /dev/null +++ b/core/src/main/java/feast/core/model/Entity.java @@ -0,0 +1,77 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2019 The Feast Authors + * + * 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 feast.core.model; + +import feast.proto.core.FeatureSetProto.EntitySpec; +import feast.proto.types.ValueProto.ValueType; +import java.util.Objects; +import javax.persistence.*; +import lombok.Getter; +import lombok.Setter; + +/** Feast entity object. Contains name and type of the entity. */ +@Getter +@Setter +@javax.persistence.Entity +@Table( + name = "entities", + uniqueConstraints = @UniqueConstraint(columnNames = {"name", "feature_set_id"})) +public class Entity { + + @Id @GeneratedValue private Long id; + + private String name; + + @ManyToOne(fetch = FetchType.LAZY) + private FeatureSet featureSet; + + /** Data type of the entity. String representation of {@link ValueType} * */ + private String type; + + public Entity() {} + + private Entity(String name, ValueType.Enum type) { + this.setName(name); + this.setType(type.toString()); + } + + public static Entity fromProto(EntitySpec entitySpec) { + Entity entity = new Entity(entitySpec.getName(), entitySpec.getValueType()); + return entity; + } + + public EntitySpec toProto() { + return EntitySpec.newBuilder().setName(name).setValueType(ValueType.Enum.valueOf(type)).build(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Entity entity = (Entity) o; + return getName().equals(entity.getName()) && getType().equals(entity.getType()); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), getName(), getType()); + } +} diff --git a/core/src/main/java/feast/core/model/Feature.java b/core/src/main/java/feast/core/model/Feature.java new file mode 100644 index 00000000000..0b457493792 --- /dev/null +++ b/core/src/main/java/feast/core/model/Feature.java @@ -0,0 +1,280 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2019 The Feast Authors + * + * 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 feast.core.model; + +import com.google.protobuf.InvalidProtocolBufferException; +import feast.core.util.TypeConversion; +import feast.proto.core.FeatureSetProto.FeatureSpec; +import feast.proto.core.FeatureSetProto.FeatureSpec.Builder; +import feast.proto.types.ValueProto.ValueType; +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; +import javax.persistence.*; +import javax.persistence.Entity; +import lombok.Getter; +import lombok.Setter; +import org.tensorflow.metadata.v0.*; + +/** + * Feature belonging to a featureset. Contains name, type as well as domain metadata about the + * feature. + */ +@Getter +@Setter +@Entity +@Table( + name = "features", + uniqueConstraints = @UniqueConstraint(columnNames = {"name", "feature_set_id"})) +public class Feature { + + @Id @GeneratedValue private Long id; + + private String name; + + @ManyToOne(fetch = FetchType.LAZY) + private FeatureSet featureSet; + + /** Data type of the feature. String representation of {@link ValueType} * */ + private String type; + + // Labels for this feature + @Column(name = "labels", columnDefinition = "text") + private String labels; + + // Presence constraints (refer to proto feast.core.FeatureSet.FeatureSpec) + // Only one of them can be set. + private byte[] presence; + private byte[] groupPresence; + + // Shape type (refer to proto feast.core.FeatureSet.FeatureSpec) + // Only one of them can be set. + private byte[] shape; + private byte[] valueCount; + + // Domain info for the values (refer to proto feast.core.FeatureSet.FeatureSpec) + // Only one of them can be set. + private String domain; + private byte[] intDomain; + private byte[] floatDomain; + private byte[] stringDomain; + private byte[] boolDomain; + private byte[] structDomain; + private byte[] naturalLanguageDomain; + private byte[] imageDomain; + private byte[] midDomain; + private byte[] urlDomain; + private byte[] timeDomain; + private byte[] timeOfDayDomain; + + public Feature() {} + // Whether this feature has been archived. A archived feature cannot be + // retrieved from or written to. + private boolean archived = false; + + private Feature(String name, ValueType.Enum type) { + this.setName(name); + this.setType(type.toString()); + } + + public static Feature fromProto(FeatureSpec featureSpec) { + Feature feature = new Feature(featureSpec.getName(), featureSpec.getValueType()); + feature.labels = TypeConversion.convertMapToJsonString(featureSpec.getLabelsMap()); + feature.updateSchema(featureSpec); + return feature; + } + + public FeatureSpec toProto() throws InvalidProtocolBufferException { + Builder featureSpecBuilder = + FeatureSpec.newBuilder().setName(getName()).setValueType(ValueType.Enum.valueOf(getType())); + + if (getPresence() != null) { + featureSpecBuilder.setPresence(FeaturePresence.parseFrom(getPresence())); + } else if (getGroupPresence() != null) { + featureSpecBuilder.setGroupPresence(FeaturePresenceWithinGroup.parseFrom(getGroupPresence())); + } + + if (getShape() != null) { + featureSpecBuilder.setShape(FixedShape.parseFrom(getShape())); + } else if (getValueCount() != null) { + featureSpecBuilder.setValueCount(ValueCount.parseFrom(getValueCount())); + } + + if (getDomain() != null) { + featureSpecBuilder.setDomain(getDomain()); + } else if (getIntDomain() != null) { + featureSpecBuilder.setIntDomain(IntDomain.parseFrom(getIntDomain())); + } else if (getFloatDomain() != null) { + featureSpecBuilder.setFloatDomain(FloatDomain.parseFrom(getFloatDomain())); + } else if (getStringDomain() != null) { + featureSpecBuilder.setStringDomain(StringDomain.parseFrom(getStringDomain())); + } else if (getBoolDomain() != null) { + featureSpecBuilder.setBoolDomain(BoolDomain.parseFrom(getBoolDomain())); + } else if (getStructDomain() != null) { + featureSpecBuilder.setStructDomain(StructDomain.parseFrom(getStructDomain())); + } else if (getNaturalLanguageDomain() != null) { + featureSpecBuilder.setNaturalLanguageDomain( + NaturalLanguageDomain.parseFrom(getNaturalLanguageDomain())); + } else if (getImageDomain() != null) { + featureSpecBuilder.setImageDomain(ImageDomain.parseFrom(getImageDomain())); + } else if (getMidDomain() != null) { + featureSpecBuilder.setMidDomain(MIDDomain.parseFrom(getMidDomain())); + } else if (getUrlDomain() != null) { + featureSpecBuilder.setUrlDomain(URLDomain.parseFrom(getUrlDomain())); + } else if (getTimeDomain() != null) { + featureSpecBuilder.setTimeDomain(TimeDomain.parseFrom(getTimeDomain())); + } else if (getTimeOfDayDomain() != null) { + featureSpecBuilder.setTimeOfDayDomain(TimeOfDayDomain.parseFrom(getTimeOfDayDomain())); + } + + if (getLabels() != null) { + featureSpecBuilder.putAllLabels(getLabels()); + } + return featureSpecBuilder.build(); + } + + private void updateSchema(FeatureSpec featureSpec) { + switch (featureSpec.getPresenceConstraintsCase()) { + case PRESENCE: + setPresence(featureSpec.getPresence().toByteArray()); + break; + case GROUP_PRESENCE: + setGroupPresence(featureSpec.getGroupPresence().toByteArray()); + break; + case PRESENCECONSTRAINTS_NOT_SET: + break; + } + + switch (featureSpec.getShapeTypeCase()) { + case SHAPE: + setShape(featureSpec.getShape().toByteArray()); + break; + case VALUE_COUNT: + setValueCount(featureSpec.getValueCount().toByteArray()); + break; + case SHAPETYPE_NOT_SET: + break; + } + + switch (featureSpec.getDomainInfoCase()) { + case DOMAIN: + setDomain(featureSpec.getDomain()); + break; + case INT_DOMAIN: + setIntDomain(featureSpec.getIntDomain().toByteArray()); + break; + case FLOAT_DOMAIN: + setFloatDomain(featureSpec.getFloatDomain().toByteArray()); + break; + case STRING_DOMAIN: + setStringDomain(featureSpec.getStringDomain().toByteArray()); + break; + case BOOL_DOMAIN: + setBoolDomain(featureSpec.getBoolDomain().toByteArray()); + break; + case STRUCT_DOMAIN: + setStructDomain(featureSpec.getStructDomain().toByteArray()); + break; + case NATURAL_LANGUAGE_DOMAIN: + setNaturalLanguageDomain(featureSpec.getNaturalLanguageDomain().toByteArray()); + break; + case IMAGE_DOMAIN: + setImageDomain(featureSpec.getImageDomain().toByteArray()); + break; + case MID_DOMAIN: + setMidDomain(featureSpec.getMidDomain().toByteArray()); + break; + case URL_DOMAIN: + setUrlDomain(featureSpec.getUrlDomain().toByteArray()); + break; + case TIME_DOMAIN: + setTimeDomain(featureSpec.getTimeDomain().toByteArray()); + break; + case TIME_OF_DAY_DOMAIN: + setTimeOfDayDomain(featureSpec.getTimeOfDayDomain().toByteArray()); + break; + case DOMAININFO_NOT_SET: + break; + } + } + + /** Archive this feature. */ + public void archive() { + this.archived = true; + } + + /** + * Update the feature object with a valid feature spec. Only schema changes are allowed. + * + * @param featureSpec {@link FeatureSpec} containing schema changes. + */ + public void updateFromProto(FeatureSpec featureSpec) { + if (isArchived()) { + throw new IllegalArgumentException( + String.format( + "You are attempting to create a feature %s that was previously archived. This isn't allowed. Please create a new feature with a different name.", + featureSpec.getName())); + } + if (ValueType.Enum.valueOf(type) != featureSpec.getValueType()) { + throw new IllegalArgumentException( + String.format( + "You are attempting to change the type of feature %s from %s to %s. This isn't allowed. Please create a new feature.", + featureSpec.getName(), type, featureSpec.getValueType())); + } + updateSchema(featureSpec); + } + + public Map getLabels() { + return TypeConversion.convertJsonStringToMap(this.labels); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Feature feature = (Feature) o; + return getName().equals(feature.getName()) + && getType().equals(feature.getType()) + && isArchived() == (feature.isArchived()) + && Objects.equals(getLabels(), feature.getLabels()) + && Arrays.equals(getPresence(), feature.getPresence()) + && Arrays.equals(getGroupPresence(), feature.getGroupPresence()) + && Arrays.equals(getShape(), feature.getShape()) + && Arrays.equals(getValueCount(), feature.getValueCount()) + && Objects.equals(getDomain(), feature.getDomain()) + && Arrays.equals(getIntDomain(), feature.getIntDomain()) + && Arrays.equals(getFloatDomain(), feature.getFloatDomain()) + && Arrays.equals(getStringDomain(), feature.getStringDomain()) + && Arrays.equals(getBoolDomain(), feature.getBoolDomain()) + && Arrays.equals(getStructDomain(), feature.getStructDomain()) + && Arrays.equals(getNaturalLanguageDomain(), feature.getNaturalLanguageDomain()) + && Arrays.equals(getImageDomain(), feature.getImageDomain()) + && Arrays.equals(getMidDomain(), feature.getMidDomain()) + && Arrays.equals(getUrlDomain(), feature.getUrlDomain()) + && Arrays.equals(getTimeDomain(), feature.getTimeDomain()) + && Arrays.equals(getTimeDomain(), feature.getTimeOfDayDomain()); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), getName(), getType(), getLabels()); + } +} diff --git a/core/src/main/java/feast/core/model/FeatureSet.java b/core/src/main/java/feast/core/model/FeatureSet.java index e4687050208..f7b2dc7cd49 100644 --- a/core/src/main/java/feast/core/model/FeatureSet.java +++ b/core/src/main/java/feast/core/model/FeatureSet.java @@ -16,57 +16,35 @@ */ package feast.core.model; +import com.google.common.collect.Sets; import com.google.protobuf.Duration; +import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Timestamp; -import feast.core.FeatureSetProto; -import feast.core.FeatureSetProto.EntitySpec; -import feast.core.FeatureSetProto.FeatureSetMeta; -import feast.core.FeatureSetProto.FeatureSetSpec; -import feast.core.FeatureSetProto.FeatureSetStatus; -import feast.core.FeatureSetProto.FeatureSpec; -import feast.types.ValueProto.ValueType; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.persistence.CascadeType; -import javax.persistence.CollectionTable; -import javax.persistence.Column; -import javax.persistence.ElementCollection; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.Table; -import javax.persistence.UniqueConstraint; +import feast.core.util.TypeConversion; +import feast.proto.core.FeatureSetProto; +import feast.proto.core.FeatureSetProto.*; +import java.util.*; +import java.util.stream.Collectors; +import javax.persistence.*; import lombok.Getter; import lombok.Setter; import org.apache.commons.lang3.builder.HashCodeBuilder; -import org.hibernate.annotations.Fetch; -import org.hibernate.annotations.FetchMode; +import org.tensorflow.metadata.v0.*; @Getter @Setter -@Entity -@Table(name = "feature_sets") -public class FeatureSet extends AbstractTimestampEntity implements Comparable { +@javax.persistence.Entity +@Table( + name = "feature_sets", + uniqueConstraints = @UniqueConstraint(columnNames = {"name", "project_name"})) +public class FeatureSet extends AbstractTimestampEntity { - // Id of the featureSet, defined as project/feature_set_name:feature_set_version - @Id - @Column(name = "id", nullable = false, unique = true) - private String id; + @Id @GeneratedValue private long id; // Name of the featureSet @Column(name = "name", nullable = false) private String name; - // Version of the featureSet - @Column(name = "version") - private int version; - // Project that this featureSet belongs to @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "project_name") @@ -77,19 +55,20 @@ public class FeatureSet extends AbstractTimestampEntity implements Comparable entities; + @OneToMany( + mappedBy = "featureSet", + cascade = CascadeType.ALL, + fetch = FetchType.EAGER, + orphanRemoval = true) + private Set entities; // Feature fields inside this feature set - @ElementCollection(fetch = FetchType.EAGER) - @CollectionTable( - name = "features", - joinColumns = @JoinColumn(name = "feature_set_id"), - uniqueConstraints = @UniqueConstraint(columnNames = {"name", "project", "version"})) - @Fetch(FetchMode.SUBSELECT) - private Set features; + @OneToMany( + mappedBy = "featureSet", + cascade = CascadeType.ALL, + fetch = FetchType.EAGER, + orphanRemoval = true) + private Set features; // Source on which feature rows can be found @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER) @@ -97,8 +76,13 @@ public class FeatureSet extends AbstractTimestampEntity implements Comparable entities, - List features, + List entities, + List features, Source source, + Map labels, FeatureSetStatus status) { this.maxAgeSeconds = maxAgeSeconds; this.source = source; - this.status = status.toString(); + this.status = status; this.entities = new HashSet<>(); this.features = new HashSet<>(); this.name = name; this.project = new Project(project); - this.version = version; - this.setId(project, name, version); + this.labels = TypeConversion.convertMapToJsonString(labels); addEntities(entities); addFeatures(features); } - private void setId(String project, String name, int version) { - this.id = project + "/" + name + ":" + version; - } - - public void setVersion(int version) { - this.version = version; - this.setId(getProjectName(), getName(), version); - } - public void setName(String name) { this.name = name; - this.setId(getProjectName(), name, getVersion()); } private String getProjectName() { @@ -150,106 +123,171 @@ private String getProjectName() { public void setProject(Project project) { this.project = project; - this.setId(project.getName(), getName(), getVersion()); } public static FeatureSet fromProto(FeatureSetProto.FeatureSet featureSetProto) { FeatureSetSpec featureSetSpec = featureSetProto.getSpec(); Source source = Source.fromProto(featureSetSpec.getSource()); - List features = new ArrayList<>(); - for (FeatureSpec feature : featureSetSpec.getFeaturesList()) { - features.add(new Field(feature.getName(), feature.getValueType())); + List featureSpecs = new ArrayList<>(); + for (FeatureSpec featureSpec : featureSetSpec.getFeaturesList()) { + featureSpecs.add(Feature.fromProto(featureSpec)); } - List entities = new ArrayList<>(); - for (EntitySpec entity : featureSetSpec.getEntitiesList()) { - entities.add(new Field(entity.getName(), entity.getValueType())); + List entitySpecs = new ArrayList<>(); + for (EntitySpec entitySpec : featureSetSpec.getEntitiesList()) { + entitySpecs.add(Entity.fromProto(entitySpec)); } return new FeatureSet( featureSetProto.getSpec().getName(), featureSetProto.getSpec().getProject(), - featureSetProto.getSpec().getVersion(), featureSetSpec.getMaxAge().getSeconds(), - entities, - features, + entitySpecs, + featureSpecs, source, + featureSetProto.getSpec().getLabelsMap(), featureSetProto.getMeta().getStatus()); } - public void addEntities(List fields) { - for (Field field : fields) { - addEntity(field); + // Updates the existing feature set from a proto. + public void updateFromProto(FeatureSetProto.FeatureSet featureSetProto) + throws InvalidProtocolBufferException { + FeatureSetSpec spec = featureSetProto.getSpec(); + if (this.toProto().getSpec().equals(spec)) { + return; + } + + // 1. validate + // 1a. check no change to identifiers + if (!name.equals(spec.getName())) { + throw new IllegalArgumentException( + String.format("Given feature set name %s does not match name %s.", spec.getName(), name)); + } + if (!project.getName().equals(spec.getProject())) { + throw new IllegalArgumentException( + String.format( + "You are attempting to change the project of feature set %s from %s to %s. This isn't allowed. Please create a new feature set under the desired project.", + spec.getName(), project, spec.getProject())); + } + + Set existingEntities = + entities.stream().map(Entity::toProto).collect(Collectors.toSet()); + + // 1b. check no change to entities + if (!Sets.newHashSet(spec.getEntitiesList()).equals(existingEntities)) { + throw new IllegalArgumentException( + String.format( + "You are attempting to change the entities of this feature set: Given set of entities \n{%s}\n does not match existing set of entities\n {%s}. This isn't allowed. Please create a new feature set. ", + spec.getEntitiesList(), existingEntities)); + } + + // 4. Update max age and source. + maxAgeSeconds = spec.getMaxAge().getSeconds(); + source = Source.fromProto(spec.getSource()); + + Map updatedFeatures = + spec.getFeaturesList().stream().collect(Collectors.toMap(FeatureSpec::getName, fs -> fs)); + + // 3. Tombstone features that are gone, update features that have changed + for (Feature existingFeature : features) { + String existingFeatureName = existingFeature.getName(); + FeatureSpec updatedFeatureSpec = updatedFeatures.get(existingFeatureName); + if (updatedFeatureSpec == null) { + existingFeature.archive(); + } else { + existingFeature.updateFromProto(updatedFeatureSpec); + updatedFeatures.remove(existingFeatureName); + } + } + + // 4. Add new features + for (FeatureSpec featureSpec : updatedFeatures.values()) { + Feature newFeature = Feature.fromProto(featureSpec); + addFeature(newFeature); } } - public void addEntity(Field field) { - field.setProject(this.project.getName()); - field.setVersion(this.getVersion()); - entities.add(field); + public void addEntities(List entities) { + for (Entity entity : entities) { + addEntity(entity); + } + } + + public void addEntity(Entity entity) { + entity.setFeatureSet(this); + entities.add(entity); } - public void addFeatures(List fields) { - for (Field field : fields) { - addFeature(field); + public void addFeatures(List features) { + for (Feature feature : features) { + addFeature(feature); } } - public void addFeature(Field field) { - field.setProject(this.project.getName()); - field.setVersion(this.getVersion()); - features.add(field); + public void addFeature(Feature feature) { + feature.setFeatureSet(this); + features.add(feature); } - public FeatureSetProto.FeatureSet toProto() { + public FeatureSetProto.FeatureSet toProto() throws InvalidProtocolBufferException { List entitySpecs = new ArrayList<>(); - for (Field entity : entities) { - entitySpecs.add( - EntitySpec.newBuilder() - .setName(entity.getName()) - .setValueType(ValueType.Enum.valueOf(entity.getType())) - .build()); + for (Entity entityField : entities) { + entitySpecs.add(entityField.toProto()); } List featureSpecs = new ArrayList<>(); - for (Field feature : features) { - featureSpecs.add( - FeatureSpec.newBuilder() - .setName(feature.getName()) - .setValueType(ValueType.Enum.valueOf(feature.getType())) - .build()); + for (Feature featureField : features) { + if (!featureField.isArchived()) { + featureSpecs.add(featureField.toProto()); + } } + FeatureSetMeta.Builder meta = FeatureSetMeta.newBuilder() .setCreatedTimestamp( Timestamp.newBuilder().setSeconds(super.getCreated().getTime() / 1000L)) - .setStatus(FeatureSetStatus.valueOf(status)); + .setStatus(status); FeatureSetSpec.Builder spec = FeatureSetSpec.newBuilder() .setName(getName()) - .setVersion(getVersion()) .setProject(project.getName()) .setMaxAge(Duration.newBuilder().setSeconds(maxAgeSeconds)) .addAllEntities(entitySpecs) .addAllFeatures(featureSpecs) + .putAllLabels(TypeConversion.convertJsonStringToMap(labels)) .setSource(source.toProto()); return FeatureSetProto.FeatureSet.newBuilder().setMeta(meta).setSpec(spec).build(); } - /** - * Checks if the given featureSet's schema and source has is different from this one. - * - * @param other FeatureSet to compare to - * @return boolean denoting if the source or schema have changed. - */ - public boolean equalTo(FeatureSet other) { + @Override + public int hashCode() { + HashCodeBuilder hcb = new HashCodeBuilder(); + hcb.append(project.getName()); + hcb.append(getName()); + return hcb.toHashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof FeatureSet)) { + return false; + } + + FeatureSet other = (FeatureSet) obj; if (!getName().equals(other.getName())) { return false; } + if (!getLabels().equals(other.getLabels())) { + return false; + } + if (!project.getName().equals(other.project.getName())) { return false; } @@ -263,65 +301,44 @@ public boolean equalTo(FeatureSet other) { } // Create a map of all fields in this feature set - Map fields = new HashMap<>(); + Map entitiesMap = new HashMap<>(); + Map featuresMap = new HashMap<>(); - for (Field e : entities) { - fields.putIfAbsent(e.getName(), e); + for (Entity e : entities) { + entitiesMap.putIfAbsent(e.getName(), e); } - for (Field f : features) { - fields.putIfAbsent(f.getName(), f); + for (Feature f : features) { + featuresMap.putIfAbsent(f.getName(), f); } // Ensure map size is consistent with existing fields - if (fields.size() != other.getFeatures().size() + other.getEntities().size()) { + if (entitiesMap.size() != other.getEntities().size()) { + return false; + } + if (featuresMap.size() != other.getFeatures().size()) { return false; } // Ensure the other entities and features exist in the field map - for (Field e : other.getEntities()) { - if (!fields.containsKey(e.getName())) { + for (Entity e : other.getEntities()) { + if (!entitiesMap.containsKey(e.getName())) { return false; } - if (!e.equals(fields.get(e.getName()))) { + if (!e.equals(entitiesMap.get(e.getName()))) { return false; } } - for (Field f : other.getFeatures()) { - if (!fields.containsKey(f.getName())) { + for (Feature f : other.getFeatures()) { + if (!featuresMap.containsKey(f.getName())) { return false; } - if (!f.equals(fields.get(f.getName()))) { + if (!f.equals(featuresMap.get(f.getName()))) { return false; } } return true; } - - @Override - public int hashCode() { - HashCodeBuilder hcb = new HashCodeBuilder(); - hcb.append(project.getName()); - hcb.append(getName()); - hcb.append(getVersion()); - return hcb.toHashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof FeatureSet)) { - return false; - } - return this.equalTo(((FeatureSet) obj)); - } - - @Override - public int compareTo(FeatureSet o) { - return Integer.compare(getVersion(), o.getVersion()); - } } diff --git a/core/src/main/java/feast/core/model/Field.java b/core/src/main/java/feast/core/model/Field.java deleted file mode 100644 index 7573fcbf5e3..00000000000 --- a/core/src/main/java/feast/core/model/Field.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright 2018-2019 The Feast Authors - * - * 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 feast.core.model; - -import feast.types.ValueProto.ValueType; -import java.util.Objects; -import javax.persistence.Column; -import javax.persistence.Embeddable; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -@Embeddable -public class Field { - - // Name of the feature - @Column(name = "name", nullable = false) - private String name; - - // Type of the feature, should correspond with feast.types.ValueType - @Column(name = "type", nullable = false) - private String type; - - // Version of the field - @Column(name = "version") - private int version; - - // Project that this field belongs to - @Column(name = "project") - private String project; - - public Field() {} - - public Field(String name, ValueType.Enum type) { - this.name = name; - this.type = type.toString(); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Field field = (Field) o; - return name.equals(field.getName()) && type.equals(field.getType()); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), name, type); - } -} diff --git a/core/src/main/java/feast/core/model/Job.java b/core/src/main/java/feast/core/model/Job.java index bbd661309d1..5fce3dffbe4 100644 --- a/core/src/main/java/feast/core/model/Job.java +++ b/core/src/main/java/feast/core/model/Job.java @@ -16,18 +16,14 @@ */ package feast.core.model; +import com.google.protobuf.InvalidProtocolBufferException; +import feast.core.job.Runner; +import feast.proto.core.FeatureSetProto; +import feast.proto.core.IngestionJobProto; +import java.util.ArrayList; import java.util.List; -import javax.persistence.CascadeType; -import javax.persistence.Column; +import javax.persistence.*; import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToMany; -import javax.persistence.ManyToOne; -import javax.persistence.OneToMany; -import javax.persistence.Table; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; @@ -49,8 +45,9 @@ public class Job extends AbstractTimestampEntity { private String extId; // Runner type + @Enumerated(EnumType.STRING) @Column(name = "runner") - private String runner; + private Runner runner; // Source id @ManyToOne @@ -63,11 +60,16 @@ public class Job extends AbstractTimestampEntity { private Store store; // FeatureSets populated by the job - @ManyToMany private List featureSets; - - // Job Metrics - @OneToMany(mappedBy = "job", cascade = CascadeType.ALL) - private List metrics; + @ManyToMany(cascade = CascadeType.ALL) + @JoinTable( + name = "jobs_feature_sets", + joinColumns = @JoinColumn(name = "job_id"), + inverseJoinColumns = @JoinColumn(name = "feature_sets_id"), + indexes = { + @Index(name = "idx_jobs_feature_sets_job_id", columnList = "job_id"), + @Index(name = "idx_jobs_feature_sets_feature_sets_id", columnList = "feature_sets_id") + }) + private List featureSets; @Enumerated(EnumType.STRING) @Column(name = "status", length = 16) @@ -77,29 +79,42 @@ public Job() { super(); } - public Job( - String id, - String extId, - String runner, - Source source, - Store sink, - List featureSets, - JobStatus jobStatus) { - this.id = id; - this.extId = extId; - this.source = source; - this.runner = runner; - this.store = sink; - this.featureSets = featureSets; - this.status = jobStatus; + public boolean hasTerminated() { + return getStatus().isTerminal(); } - public void updateMetrics(List newMetrics) { - metrics.clear(); - metrics.addAll(newMetrics); + public boolean isRunning() { + return getStatus() == JobStatus.RUNNING; } public String getSinkName() { return store.getName(); } + + /** + * Convert a job model to ingestion job proto + * + * @return Ingestion Job proto derieved from the given job + */ + public IngestionJobProto.IngestionJob toProto() throws InvalidProtocolBufferException { + + // convert featuresets of job to protos + List featureSetProtos = new ArrayList<>(); + for (FeatureSet featureSet : this.getFeatureSets()) { + featureSetProtos.add(featureSet.toProto()); + } + + // build ingestion job proto with job data + IngestionJobProto.IngestionJob ingestJob = + IngestionJobProto.IngestionJob.newBuilder() + .setId(this.getId()) + .setExternalId(this.getExtId()) + .setStatus(this.getStatus().toProto()) + .addAllFeatureSets(featureSetProtos) + .setSource(this.getSource().toProto()) + .setStore(this.getStore().toProto()) + .build(); + + return ingestJob; + } } diff --git a/core/src/main/java/feast/core/model/JobStatus.java b/core/src/main/java/feast/core/model/JobStatus.java index 123b57a21b1..6bafc06ec90 100644 --- a/core/src/main/java/feast/core/model/JobStatus.java +++ b/core/src/main/java/feast/core/model/JobStatus.java @@ -16,9 +16,9 @@ */ package feast.core.model; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; +import feast.proto.core.IngestionJobProto.IngestionJobStatus; +import java.util.Map; +import java.util.Set; public enum JobStatus { /** Job status is not known. */ @@ -51,17 +51,60 @@ public enum JobStatus { /** job has been suspended */ SUSPENDED; - private static final Collection TERMINAL_STATE = - Collections.unmodifiableList(Arrays.asList(COMPLETED, ABORTED, ERROR)); + private static final Set TERMINAL_STATES = Set.of(COMPLETED, ABORTED, ERROR); /** - * Get a collection of terminal job state. + * Get the set of terminal job states. * - *

Terminal job state is final and will not change to any other state. + *

A terminal job state is final and will not change to any other state. * - * @return collection of terminal job state. + * @return set of terminal job states. */ - public static Collection getTerminalState() { - return TERMINAL_STATE; + public static Set getTerminalStates() { + return TERMINAL_STATES; + } + + private static final Set TRANSITIONAL_STATES = Set.of(PENDING, ABORTING, SUSPENDING); + + /** + * Get Transitional Job Status states. Transitional states are assigned to jobs that are + * transitioning to a more stable state (ie SUSPENDED, ABORTED etc.) + * + * @return set of transitional Job Status states. + */ + public static Set getTransitionalStates() { + return TRANSITIONAL_STATES; + } + + /** @return true if this {@code JobStatus} is a terminal state. */ + public boolean isTerminal() { + return getTerminalStates().contains(this); + } + + /** @return true if this {@code JobStatus} is a transitional state. */ + public boolean isTransitional() { + return getTransitionalStates().contains(this); + } + + private static final Map INGESTION_JOB_STATUS_MAP = + Map.of( + JobStatus.UNKNOWN, IngestionJobStatus.UNKNOWN, + JobStatus.PENDING, IngestionJobStatus.PENDING, + JobStatus.RUNNING, IngestionJobStatus.RUNNING, + JobStatus.COMPLETED, IngestionJobStatus.COMPLETED, + JobStatus.ABORTING, IngestionJobStatus.ABORTING, + JobStatus.ABORTED, IngestionJobStatus.ABORTED, + JobStatus.ERROR, IngestionJobStatus.ERROR, + JobStatus.SUSPENDING, IngestionJobStatus.SUSPENDING, + JobStatus.SUSPENDED, IngestionJobStatus.SUSPENDED); + + /** + * Convert a Job Status to Ingestion Job Status proto + * + * @return IngestionJobStatus proto derived from this job status + */ + public IngestionJobStatus toProto() { + // maps job models job status to ingestion job status + return INGESTION_JOB_STATUS_MAP.get(this); } } diff --git a/core/src/main/java/feast/core/model/Metrics.java b/core/src/main/java/feast/core/model/Metrics.java deleted file mode 100644 index 0b7514816fa..00000000000 --- a/core/src/main/java/feast/core/model/Metrics.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright 2018-2019 The Feast Authors - * - * 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 feast.core.model; - -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.Table; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@NoArgsConstructor -@Getter -@Setter -@Entity -@Table(name = "metrics") -public class Metrics extends AbstractTimestampEntity { - - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - private long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "job_id") - private Job job; - - /** Metrics name */ - private String name; - - /** Metrics value */ - private double value; - - /** - * Create a metrics owned by a {@code job}. - * - * @param job owner of this metrics. - * @param metricsName metrics name. - * @param value metrics value. - */ - public Metrics(Job job, String metricsName, double value) { - this.job = job; - this.name = metricsName; - this.value = value; - } -} diff --git a/core/src/main/java/feast/core/model/Project.java b/core/src/main/java/feast/core/model/Project.java index d6e6149394b..c55830c8248 100644 --- a/core/src/main/java/feast/core/model/Project.java +++ b/core/src/main/java/feast/core/model/Project.java @@ -34,6 +34,7 @@ @Entity @Table(name = "projects") public class Project { + public static final String DEFAULT_NAME = "default"; // Name of the project @Id diff --git a/core/src/main/java/feast/core/model/Source.java b/core/src/main/java/feast/core/model/Source.java index 28db1e9a5ba..d199a1b676a 100644 --- a/core/src/main/java/feast/core/model/Source.java +++ b/core/src/main/java/feast/core/model/Source.java @@ -18,10 +18,10 @@ import com.google.common.collect.Sets; import com.google.protobuf.Message; -import feast.core.SourceProto; -import feast.core.SourceProto.KafkaSourceConfig; -import feast.core.SourceProto.Source.Builder; -import feast.core.SourceProto.SourceType; +import feast.proto.core.SourceProto; +import feast.proto.core.SourceProto.KafkaSourceConfig; +import feast.proto.core.SourceProto.Source.Builder; +import feast.proto.core.SourceProto.SourceType; import io.grpc.Status; import java.util.Objects; import java.util.Set; diff --git a/core/src/main/java/feast/core/model/Store.java b/core/src/main/java/feast/core/model/Store.java index 9dc44bdc73a..1f7c373bdc5 100644 --- a/core/src/main/java/feast/core/model/Store.java +++ b/core/src/main/java/feast/core/model/Store.java @@ -17,13 +17,14 @@ package feast.core.model; import com.google.protobuf.InvalidProtocolBufferException; -import feast.core.StoreProto; -import feast.core.StoreProto.Store.BigQueryConfig; -import feast.core.StoreProto.Store.Builder; -import feast.core.StoreProto.Store.CassandraConfig; -import feast.core.StoreProto.Store.RedisConfig; -import feast.core.StoreProto.Store.StoreType; -import feast.core.StoreProto.Store.Subscription; +import feast.proto.core.StoreProto; +import feast.proto.core.StoreProto.Store.BigQueryConfig; +import feast.proto.core.StoreProto.Store.Builder; +import feast.proto.core.StoreProto.Store.CassandraConfig; +import feast.proto.core.StoreProto.Store.RedisClusterConfig; +import feast.proto.core.StoreProto.Store.RedisConfig; +import feast.proto.core.StoreProto.Store.StoreType; +import feast.proto.core.StoreProto.Store.Subscription; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -82,6 +83,9 @@ public static Store fromProto(StoreProto.Store storeProto) throws IllegalArgumen case CASSANDRA: config = storeProto.getCassandraConfig().toByteArray(); break; + case REDIS_CLUSTER: + config = storeProto.getRedisClusterConfig().toByteArray(); + break; default: throw new IllegalArgumentException("Invalid store provided"); } @@ -106,6 +110,9 @@ public StoreProto.Store toProto() throws InvalidProtocolBufferException { case CASSANDRA: CassandraConfig cassConfig = CassandraConfig.parseFrom(config); return storeProtoBuilder.setCassandraConfig(cassConfig).build(); + case REDIS_CLUSTER: + RedisClusterConfig redisClusterConfig = RedisClusterConfig.parseFrom(config); + return storeProtoBuilder.setRedisClusterConfig(redisClusterConfig).build(); default: throw new InvalidProtocolBufferException("Invalid store set"); } @@ -118,22 +125,18 @@ public List getSubscriptions() { } private static String convertSubscriptionToString(Subscription sub) { - if (sub.getVersion().isEmpty() || sub.getName().isEmpty() || sub.getProject().isEmpty()) { + if (sub.getName().isEmpty() || sub.getProject().isEmpty()) { throw new IllegalArgumentException( String.format("Missing arguments in subscription string: %s", sub.toString())); } - return String.format("%s:%s:%s", sub.getProject(), sub.getName(), sub.getVersion()); + return String.format("%s:%s", sub.getProject(), sub.getName()); } private Subscription convertStringToSubscription(String sub) { if (sub.equals("")) { return Subscription.newBuilder().build(); } - String[] split = sub.split(":", 3); - return Subscription.newBuilder() - .setProject(split[0]) - .setName(split[1]) - .setVersion(split[2]) - .build(); + String[] split = sub.split(":", 2); + return Subscription.newBuilder().setProject(split[0]).setName(split[1]).build(); } } diff --git a/core/src/main/java/feast/core/service/AccessManagementService.java b/core/src/main/java/feast/core/service/AccessManagementService.java index 6f627df33d6..5b02d6f3c4a 100644 --- a/core/src/main/java/feast/core/service/AccessManagementService.java +++ b/core/src/main/java/feast/core/service/AccessManagementService.java @@ -28,12 +28,15 @@ @Slf4j @Service public class AccessManagementService { - private ProjectRepository projectRepository; @Autowired public AccessManagementService(ProjectRepository projectRepository) { this.projectRepository = projectRepository; + // create default project if it does not yet exist. + if (!projectRepository.existsById(Project.DEFAULT_NAME)) { + this.createProject(Project.DEFAULT_NAME); + } } /** @@ -61,6 +64,9 @@ public void archiveProject(String name) { if (!project.isPresent()) { throw new IllegalArgumentException(String.format("Could not find project: \"%s\"", name)); } + if (name.equals(Project.DEFAULT_NAME)) { + throw new UnsupportedOperationException("Archiving the default project is not allowed."); + } Project p = project.get(); p.setArchived(true); projectRepository.saveAndFlush(p); @@ -71,7 +77,6 @@ public void archiveProject(String name) { * * @return List of active projects */ - @Transactional public List listProjects() { return projectRepository.findAllByArchivedIsFalse(); } diff --git a/core/src/main/java/feast/core/service/JobCoordinatorService.java b/core/src/main/java/feast/core/service/JobCoordinatorService.java index 23ad041b81d..90ee54ca166 100644 --- a/core/src/main/java/feast/core/service/JobCoordinatorService.java +++ b/core/src/main/java/feast/core/service/JobCoordinatorService.java @@ -16,23 +16,22 @@ */ package feast.core.service; -import feast.core.CoreServiceProto.ListFeatureSetsRequest; -import feast.core.CoreServiceProto.ListStoresRequest.Filter; -import feast.core.CoreServiceProto.ListStoresResponse; -import feast.core.FeatureSetProto; -import feast.core.FeatureSetProto.FeatureSetStatus; -import feast.core.StoreProto; -import feast.core.StoreProto.Store.Subscription; -import feast.core.config.FeastProperties.JobUpdatesProperties; +import com.google.protobuf.InvalidProtocolBufferException; +import feast.core.config.FeastProperties; +import feast.core.config.FeastProperties.JobProperties; import feast.core.dao.FeatureSetRepository; import feast.core.dao.JobRepository; import feast.core.job.JobManager; import feast.core.job.JobUpdateTask; import feast.core.model.FeatureSet; import feast.core.model.Job; -import feast.core.model.JobStatus; import feast.core.model.Source; import feast.core.model.Store; +import feast.proto.core.CoreServiceProto.ListStoresRequest.Filter; +import feast.proto.core.CoreServiceProto.ListStoresResponse; +import feast.proto.core.FeatureSetProto.FeatureSetStatus; +import feast.proto.core.StoreProto; +import feast.proto.core.StoreProto.Store.Subscription; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -43,6 +42,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.stream.Collectors; +import javax.validation.constraints.Positive; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; @@ -53,12 +53,11 @@ @Service public class JobCoordinatorService { - private final long POLLING_INTERVAL_MILLISECONDS = 60000; // 1 min - private JobRepository jobRepository; - private FeatureSetRepository featureSetRepository; - private SpecService specService; - private JobManager jobManager; - private JobUpdatesProperties jobUpdatesProperties; + private final JobRepository jobRepository; + private final FeatureSetRepository featureSetRepository; + private final SpecService specService; + private final JobManager jobManager; + private final JobProperties jobProperties; @Autowired public JobCoordinatorService( @@ -66,12 +65,12 @@ public JobCoordinatorService( FeatureSetRepository featureSetRepository, SpecService specService, JobManager jobManager, - JobUpdatesProperties jobUpdatesProperties) { + FeastProperties feastProperties) { this.jobRepository = jobRepository; this.featureSetRepository = featureSetRepository; this.specService = specService; this.jobManager = jobManager; - this.jobUpdatesProperties = jobUpdatesProperties; + this.jobProperties = feastProperties.getJobs(); } /** @@ -86,98 +85,93 @@ public JobCoordinatorService( *

4) Updates Feature set statuses */ @Transactional - @Scheduled(fixedDelay = POLLING_INTERVAL_MILLISECONDS) - public void Poll() { + @Scheduled(fixedDelayString = "${feast.jobs.polling_interval_milliseconds}") + public void Poll() throws InvalidProtocolBufferException { log.info("Polling for new jobs..."); + @Positive long updateTimeout = jobProperties.getJobUpdateTimeoutSeconds(); List jobUpdateTasks = new ArrayList<>(); ListStoresResponse listStoresResponse = specService.listStores(Filter.newBuilder().build()); - for (StoreProto.Store store : listStoresResponse.getStoreList()) { - Set featureSets = new HashSet<>(); - for (Subscription subscription : store.getSubscriptionsList()) { - featureSets.addAll( - new ArrayList<>( - specService - .listFeatureSets( - ListFeatureSetsRequest.Filter.newBuilder() - .setFeatureSetName(subscription.getName()) - .setFeatureSetVersion(subscription.getVersion()) - .setProject(subscription.getProject()) - .build()) - .getFeatureSetsList())); - } - if (!featureSets.isEmpty()) { - featureSets.stream() - .collect(Collectors.groupingBy(fs -> fs.getSpec().getSource())) - .entrySet() - .stream() - .forEach( - kv -> { - Optional originalJob = - getJob(Source.fromProto(kv.getKey()), Store.fromProto(store)); - jobUpdateTasks.add( - new JobUpdateTask( - kv.getValue(), - kv.getKey(), - store, - originalJob, - jobManager, - jobUpdatesProperties.getTimeoutSeconds())); - }); + + for (StoreProto.Store storeSpec : listStoresResponse.getStoreList()) { + Set featureSets = new HashSet<>(); + Store store = Store.fromProto(storeSpec); + + for (Subscription subscription : store.getSubscriptions()) { + List featureSetsForSub = + featureSetRepository.findAllByNameLikeAndProject_NameLikeOrderByNameAsc( + subscription.getName().replace('*', '%'), + subscription.getProject().replace('*', '%')); + featureSets.addAll(featureSetsForSub); } + + featureSets.stream() + .collect(Collectors.groupingBy(FeatureSet::getSource)) + .forEach( + (source, setsForSource) -> { + Optional originalJob = getJob(source, store); + jobUpdateTasks.add( + new JobUpdateTask( + setsForSource, source, store, originalJob, jobManager, updateTimeout)); + }); } - if (jobUpdateTasks.size() == 0) { + if (jobUpdateTasks.isEmpty()) { log.info("No jobs found."); return; } log.info("Creating/Updating {} jobs...", jobUpdateTasks.size()); - ExecutorService executorService = Executors.newFixedThreadPool(jobUpdateTasks.size()); + startOrUpdateJobs(jobUpdateTasks); + + log.info("Updating feature set status"); + updateFeatureSetStatuses(jobUpdateTasks); + } + + void startOrUpdateJobs(List tasks) { + ExecutorService executorService = Executors.newFixedThreadPool(tasks.size()); ExecutorCompletionService ecs = new ExecutorCompletionService<>(executorService); - jobUpdateTasks.forEach(ecs::submit); + tasks.forEach(ecs::submit); int completedTasks = 0; - while (completedTasks < jobUpdateTasks.size()) { + List startedJobs = new ArrayList<>(); + while (completedTasks < tasks.size()) { try { Job job = ecs.take().get(); if (job != null) { - jobRepository.saveAndFlush(job); + startedJobs.add(job); } } catch (ExecutionException | InterruptedException e) { log.warn("Unable to start or update job: {}", e.getMessage()); } completedTasks++; } - - log.info("Updating feature set status"); - updateFeatureSetStatuses(jobUpdateTasks); + jobRepository.saveAll(startedJobs); + executorService.shutdown(); } // TODO: make this more efficient private void updateFeatureSetStatuses(List jobUpdateTasks) { Set ready = new HashSet<>(); Set pending = new HashSet<>(); - for (JobUpdateTask jobUpdateTask : jobUpdateTasks) { - Optional job = - getJob( - Source.fromProto(jobUpdateTask.getSourceSpec()), - Store.fromProto(jobUpdateTask.getStore())); - if (job.isPresent()) { - if (job.get().getStatus() == JobStatus.RUNNING) { - ready.addAll(job.get().getFeatureSets()); - } else { - pending.addAll(job.get().getFeatureSets()); - } - } + for (JobUpdateTask task : jobUpdateTasks) { + getJob(task.getSource(), task.getStore()) + .ifPresent( + job -> { + if (job.isRunning()) { + ready.addAll(job.getFeatureSets()); + } else { + pending.addAll(job.getFeatureSets()); + } + }); } ready.removeAll(pending); ready.forEach( fs -> { - fs.setStatus(FeatureSetStatus.STATUS_READY.toString()); + fs.setStatus(FeatureSetStatus.STATUS_READY); featureSetRepository.save(fs); }); pending.forEach( fs -> { - fs.setStatus(FeatureSetStatus.STATUS_PENDING.toString()); + fs.setStatus(FeatureSetStatus.STATUS_JOB_STARTING); featureSetRepository.save(fs); }); featureSetRepository.flush(); @@ -188,11 +182,8 @@ public Optional getJob(Source source, Store store) { List jobs = jobRepository.findBySourceIdAndStoreNameOrderByLastUpdatedDesc( source.getId(), store.getName()); - jobs = - jobs.stream() - .filter(job -> !JobStatus.getTerminalState().contains(job.getStatus())) - .collect(Collectors.toList()); - if (jobs.size() == 0) { + jobs = jobs.stream().filter(job -> !job.hasTerminated()).collect(Collectors.toList()); + if (jobs.isEmpty()) { return Optional.empty(); } // return the latest diff --git a/core/src/main/java/feast/core/service/JobService.java b/core/src/main/java/feast/core/service/JobService.java new file mode 100644 index 00000000000..cc125305ec3 --- /dev/null +++ b/core/src/main/java/feast/core/service/JobService.java @@ -0,0 +1,281 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.core.service; + +import com.google.protobuf.InvalidProtocolBufferException; +import feast.core.dao.JobRepository; +import feast.core.job.JobManager; +import feast.core.job.Runner; +import feast.core.log.Action; +import feast.core.log.AuditLogger; +import feast.core.log.Resource; +import feast.core.model.FeatureSet; +import feast.core.model.Job; +import feast.core.model.JobStatus; +import feast.proto.core.CoreServiceProto.ListFeatureSetsRequest; +import feast.proto.core.CoreServiceProto.ListFeatureSetsResponse; +import feast.proto.core.CoreServiceProto.ListIngestionJobsRequest; +import feast.proto.core.CoreServiceProto.ListIngestionJobsResponse; +import feast.proto.core.CoreServiceProto.RestartIngestionJobRequest; +import feast.proto.core.CoreServiceProto.RestartIngestionJobResponse; +import feast.proto.core.CoreServiceProto.StopIngestionJobRequest; +import feast.proto.core.CoreServiceProto.StopIngestionJobResponse; +import feast.proto.core.FeatureSetReferenceProto.FeatureSetReference; +import feast.proto.core.IngestionJobProto; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** A Job Management Service that allows users to manage Feast ingestion jobs. */ +@Slf4j +@Service +public class JobService { + private final JobRepository jobRepository; + private final SpecService specService; + private final Map jobManagers; + + @Autowired + public JobService( + JobRepository jobRepository, SpecService specService, List jobManagerList) { + this.jobRepository = jobRepository; + this.specService = specService; + + this.jobManagers = new HashMap<>(); + for (JobManager manager : jobManagerList) { + this.jobManagers.put(manager.getRunnerType(), manager); + } + } + + /* Job Service API */ + /** + * List Ingestion Jobs in Feast matching the given request. See CoreService protobuf documentation + * for more detailed documentation. + * + * @param request list ingestion jobs request specifying which jobs to include + * @throws IllegalArgumentException when given filter in a unsupported configuration + * @throws InvalidProtocolBufferException on error when constructing response protobuf + * @return list ingestion jobs response + */ + @Transactional(readOnly = true) + public ListIngestionJobsResponse listJobs(ListIngestionJobsRequest request) + throws InvalidProtocolBufferException { + Set matchingJobIds = new HashSet<>(); + + // check that filter specified and not empty + if (request.hasFilter() + && !(request.getFilter().getId() == "" + && request.getFilter().getStoreName() == "" + && request.getFilter().hasFeatureSetReference() == false)) { + // filter jobs based on request filter + ListIngestionJobsRequest.Filter filter = request.getFilter(); + + // for proto3, default value for missing values: + // - numeric values (ie int) is zero + // - strings is empty string + if (filter.getId() != "") { + // get by id: no more filters required: found job + Optional job = this.jobRepository.findById(filter.getId()); + if (job.isPresent()) { + matchingJobIds.add(filter.getId()); + } + } else { + // multiple filters can apply together in an 'and' operation + if (filter.getStoreName() != "") { + // find jobs by name + List jobs = this.jobRepository.findByStoreName(filter.getStoreName()); + Set jobIds = jobs.stream().map(Job::getId).collect(Collectors.toSet()); + matchingJobIds = this.mergeResults(matchingJobIds, jobIds); + } + if (filter.hasFeatureSetReference()) { + // find a matching featuresets for reference + FeatureSetReference fsReference = filter.getFeatureSetReference(); + ListFeatureSetsResponse response = + this.specService.listFeatureSets(this.toListFeatureSetFilter(fsReference)); + List featureSets = + response.getFeatureSetsList().stream() + .map(FeatureSet::fromProto) + .collect(Collectors.toList()); + + // find jobs for the matching featuresets + Collection matchingJobs = this.jobRepository.findByFeatureSetsIn(featureSets); + List jobIds = matchingJobs.stream().map(Job::getId).collect(Collectors.toList()); + matchingJobIds = this.mergeResults(matchingJobIds, jobIds); + } + } + } else { + // no or empty filter: match all jobs + matchingJobIds = + this.jobRepository.findAll().stream().map(Job::getId).collect(Collectors.toSet()); + } + + // convert matching job models to ingestion job protos + List ingestJobs = new ArrayList<>(); + for (String jobId : matchingJobIds) { + Job job = this.jobRepository.findById(jobId).get(); + ingestJobs.add(job.toProto()); + } + + // pack jobs into response + return ListIngestionJobsResponse.newBuilder().addAllJobs(ingestJobs).build(); + } + + /** + * Restart (Aborts) the ingestion job matching the given restart request. See CoreService protobuf + * documentation for more detailed documentation. + * + * @param request restart ingestion job request specifying which job to stop + * @throws NoSuchElementException when restart job request requests to restart a nonexistent job. + * @throws UnsupportedOperationException when job to be restarted is in an unsupported status + * @throws InvalidProtocolBufferException on error when constructing response protobuf + */ + @Transactional + public RestartIngestionJobResponse restartJob(RestartIngestionJobRequest request) + throws InvalidProtocolBufferException { + // check job exists + Optional getJob = this.jobRepository.findById(request.getId()); + if (getJob.isEmpty()) { + // FIXME: if getJob.isEmpty then constructing this error message will always throw an error... + throw new NoSuchElementException( + "Attempted to stop nonexistent job with id: " + getJob.get().getId()); + } + + // check job status is valid for restarting + Job job = getJob.get(); + JobStatus status = job.getStatus(); + if (status.isTransitional() || status.isTerminal() || status == JobStatus.UNKNOWN) { + throw new UnsupportedOperationException( + "Restarting a job with a transitional, terminal or unknown status is unsupported"); + } + + // restart job with job manager + JobManager jobManager = this.jobManagers.get(job.getRunner()); + job = jobManager.restartJob(job); + log.info( + String.format( + "Restarted job (id: %s, extId: %s runner: %s)", + job.getId(), job.getExtId(), job.getRunner())); + // sync job status & update job model in job repository + job = this.syncJobStatus(jobManager, job); + this.jobRepository.saveAndFlush(job); + + return RestartIngestionJobResponse.newBuilder().build(); + } + + /** + * Stops (Aborts) the ingestion job matching the given stop request. See CoreService protobuf + * documentation for more detailed documentation. + * + * @param request stop ingestion job request specifying which job to stop + * @throws NoSuchElementException when stop job request requests to stop a nonexistent job. + * @throws UnsupportedOperationException when job to be stopped is in an unsupported status + * @throws InvalidProtocolBufferException on error when constructing response protobuf + */ + @Transactional + public StopIngestionJobResponse stopJob(StopIngestionJobRequest request) + throws InvalidProtocolBufferException { + // check job exists + Optional getJob = this.jobRepository.findById(request.getId()); + if (getJob.isEmpty()) { + throw new NoSuchElementException( + "Attempted to stop nonexistent job with id: " + getJob.get().getId()); + } + + // check job status is valid for stopping + Job job = getJob.get(); + JobStatus status = job.getStatus(); + if (status.isTerminal()) { + // do nothing - job is already stopped + return StopIngestionJobResponse.newBuilder().build(); + } else if (status.isTransitional() || status == JobStatus.UNKNOWN) { + throw new UnsupportedOperationException( + "Stopping a job with a transitional or unknown status is unsupported"); + } + + // stop job with job manager + JobManager jobManager = this.jobManagers.get(job.getRunner()); + jobManager.abortJob(job.getExtId()); + log.info( + String.format( + "Restarted job (id: %s, extId: %s runner: %s)", + job.getId(), job.getExtId(), job.getRunner())); + + // sync job status & update job model in job repository + job = this.syncJobStatus(jobManager, job); + this.jobRepository.saveAndFlush(job); + + return StopIngestionJobResponse.newBuilder().build(); + } + + /* Private Utility Methods */ + private Set mergeResults(Set results, Collection newResults) { + if (results.size() <= 0) { + // no existing results: copy over new results + results.addAll(newResults); + } else { + // and operation: keep results that exist in both existing and new results + results.retainAll(newResults); + } + return results; + } + + // converts feature set reference to a list feature set filter + private ListFeatureSetsRequest.Filter toListFeatureSetFilter(FeatureSetReference fsReference) { + // match featuresets using contents of featureset reference + String fsName = fsReference.getName(); + String fsProject = fsReference.getProject(); + + // construct list featureset request filter using feature set reference + // for proto3, default value for missing values: + // - numeric values (ie int) is zero + // - strings is empty string + ListFeatureSetsRequest.Filter filter = + ListFeatureSetsRequest.Filter.newBuilder() + .setFeatureSetName((fsName != "") ? fsName : "*") + .setProject((fsProject != "") ? fsProject : "*") + .build(); + + return filter; + } + + // sync job status using job manager + private Job syncJobStatus(JobManager jobManager, Job job) { + JobStatus newStatus = jobManager.getJobStatus(job); + // log job status transition + if (newStatus != job.getStatus()) { + AuditLogger.log( + Resource.JOB, + job.getId(), + Action.STATUS_CHANGE, + "Job status transition: changed from %s to %s", + job.getStatus(), + newStatus); + job.setStatus(newStatus); + } + return job; + } +} diff --git a/core/src/main/java/feast/core/service/SpecService.java b/core/src/main/java/feast/core/service/SpecService.java index 1d6ce16de54..01cd264c761 100644 --- a/core/src/main/java/feast/core/service/SpecService.java +++ b/core/src/main/java/feast/core/service/SpecService.java @@ -19,23 +19,7 @@ import static feast.core.validators.Matchers.checkValidCharacters; import static feast.core.validators.Matchers.checkValidCharactersAllowAsterisk; -import com.google.common.collect.Ordering; import com.google.protobuf.InvalidProtocolBufferException; -import feast.core.CoreServiceProto.ApplyFeatureSetResponse; -import feast.core.CoreServiceProto.ApplyFeatureSetResponse.Status; -import feast.core.CoreServiceProto.GetFeatureSetRequest; -import feast.core.CoreServiceProto.GetFeatureSetResponse; -import feast.core.CoreServiceProto.ListFeatureSetsRequest; -import feast.core.CoreServiceProto.ListFeatureSetsResponse; -import feast.core.CoreServiceProto.ListStoresRequest; -import feast.core.CoreServiceProto.ListStoresResponse; -import feast.core.CoreServiceProto.ListStoresResponse.Builder; -import feast.core.CoreServiceProto.UpdateStoreRequest; -import feast.core.CoreServiceProto.UpdateStoreResponse; -import feast.core.FeatureSetProto; -import feast.core.SourceProto; -import feast.core.StoreProto; -import feast.core.StoreProto.Store.Subscription; import feast.core.dao.FeatureSetRepository; import feast.core.dao.ProjectRepository; import feast.core.dao.StoreRepository; @@ -45,10 +29,25 @@ import feast.core.model.Source; import feast.core.model.Store; import feast.core.validators.FeatureSetValidator; +import feast.proto.core.CoreServiceProto.ApplyFeatureSetResponse; +import feast.proto.core.CoreServiceProto.ApplyFeatureSetResponse.Status; +import feast.proto.core.CoreServiceProto.GetFeatureSetRequest; +import feast.proto.core.CoreServiceProto.GetFeatureSetResponse; +import feast.proto.core.CoreServiceProto.ListFeatureSetsRequest; +import feast.proto.core.CoreServiceProto.ListFeatureSetsResponse; +import feast.proto.core.CoreServiceProto.ListStoresRequest; +import feast.proto.core.CoreServiceProto.ListStoresResponse; +import feast.proto.core.CoreServiceProto.ListStoresResponse.Builder; +import feast.proto.core.CoreServiceProto.UpdateStoreRequest; +import feast.proto.core.CoreServiceProto.UpdateStoreResponse; +import feast.proto.core.FeatureSetProto; +import feast.proto.core.FeatureSetProto.FeatureSetStatus; +import feast.proto.core.SourceProto; +import feast.proto.core.StoreProto; +import feast.proto.core.StoreProto.Store.Subscription; import java.util.ArrayList; import java.util.List; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -81,12 +80,14 @@ public SpecService( /** * Get a feature set matching the feature name and version and project. The feature set name and * project are required, but version can be omitted by providing 0 for its value. If the version - * is omitted, the latest feature set will be provided. + * is omitted, the latest feature set will be provided. If the project is omitted, the default + * would be used. * * @param request: GetFeatureSetRequest Request containing filter parameters. * @return Returns a GetFeatureSetResponse containing a feature set.. */ - public GetFeatureSetResponse getFeatureSet(GetFeatureSetRequest request) { + public GetFeatureSetResponse getFeatureSet(GetFeatureSetRequest request) + throws InvalidProtocolBufferException { // Validate input arguments checkValidCharacters(request.getName(), "featureSetName"); @@ -94,129 +95,88 @@ public GetFeatureSetResponse getFeatureSet(GetFeatureSetRequest request) { if (request.getName().isEmpty()) { throw new IllegalArgumentException("No feature set name provided"); } + // Autofill default project if project is not specified if (request.getProject().isEmpty()) { - throw new IllegalArgumentException("No project provided"); - } - if (request.getVersion() < 0) { - throw new IllegalArgumentException("Version number cannot be less than 0"); + request = request.toBuilder().setProject(Project.DEFAULT_NAME).build(); } FeatureSet featureSet; - // Filter the list based on version - if (request.getVersion() == 0) { - featureSet = - featureSetRepository.findFirstFeatureSetByNameLikeAndProject_NameOrderByVersionDesc( - request.getName(), request.getProject()); + featureSet = + featureSetRepository.findFeatureSetByNameAndProject_Name( + request.getName(), request.getProject()); - if (featureSet == null) { - throw new RetrievalException( - String.format("Feature set with name \"%s\" could not be found.", request.getName())); - } - } else { - featureSet = - featureSetRepository.findFeatureSetByNameAndProject_NameAndVersion( - request.getName(), request.getProject(), request.getVersion()); - - if (featureSet == null) { - throw new RetrievalException( - String.format( - "Feature set with name \"%s\" and version \"%s\" could " + "not be found.", - request.getName(), request.getVersion())); - } + if (featureSet == null) { + throw new RetrievalException( + String.format("Feature set with name \"%s\" could not be found.", request.getName())); } - - // Only a single item in list, return successfully return GetFeatureSetResponse.newBuilder().setFeatureSet(featureSet.toProto()).build(); } /** - * Return a list of feature sets matching the feature set name, version, and project provided in - * the filter. All fields are requried. Use '*' for all three arguments in order to return all - * feature sets and versions in all projects. + * Return a list of feature sets matching the feature set name and project provided in the filter. + * All fields are requried. Use '*' for all arguments in order to return all feature sets in all + * projects. * *

Project name can be explicitly provided, or an asterisk can be provided to match all - * projects. It is not possible to provide a combination of asterisks/wildcards and text. + * projects. It is not possible to provide a combination of asterisks/wildcards and text. If the + * project name is omitted, the default project would be used. * *

The feature set name in the filter accepts an asterisk as a wildcard. All matching feature * sets will be returned. Regex is not supported. Explicitly defining a feature set name is not * possible if a project name is not set explicitly * - *

The version field can be one of - '*' - This will match all versions - 'latest' - This will - * match the latest feature set version - '<number>' - This will match a specific feature set - * version. This property can only be set if both the feature set name and project name are - * explicitly set. - * - * @param filter filter containing the desired featureSet name and version filter + * @param filter filter containing the desired featureSet name * @return ListFeatureSetsResponse with list of featureSets found matching the filter */ - public ListFeatureSetsResponse listFeatureSets(ListFeatureSetsRequest.Filter filter) { + public ListFeatureSetsResponse listFeatureSets(ListFeatureSetsRequest.Filter filter) + throws InvalidProtocolBufferException { String name = filter.getFeatureSetName(); String project = filter.getProject(); - String version = filter.getFeatureSetVersion(); - if (project.isEmpty() || name.isEmpty() || version.isEmpty()) { + if (name.isEmpty()) { throw new IllegalArgumentException( - String.format( - "Invalid listFeatureSetRequest, missing arguments. Must provide project, feature set name, and version.", - filter.toString())); + "Invalid listFeatureSetRequest, missing arguments. Must provide feature set name:"); } checkValidCharactersAllowAsterisk(name, "featureSetName"); checkValidCharactersAllowAsterisk(project, "projectName"); - List featureSets = new ArrayList() {}; + // Autofill default project if project not specified + if (project.isEmpty()) { + project = Project.DEFAULT_NAME; + } - if (project.equals("*")) { - // Matching all projects + List featureSets = new ArrayList() {}; - if (name.equals("*") && version.equals("*")) { + if (project.contains("*")) { + // Matching a wildcard project + if (name.contains("*")) { featureSets = - featureSetRepository.findAllByNameLikeAndProject_NameLikeOrderByNameAscVersionAsc( + featureSetRepository.findAllByNameLikeAndProject_NameLikeOrderByNameAsc( name.replace('*', '%'), project.replace('*', '%')); } else { throw new IllegalArgumentException( String.format( - "Invalid listFeatureSetRequest. Version and feature set name must be set to " + "Invalid listFeatureSetRequest. Feature set name must be set to " + "\"*\" if the project name and feature set name aren't set explicitly: \n%s", filter.toString())); } } else if (!project.contains("*")) { // Matching a specific project - - if (name.contains("*") && version.equals("*")) { - // Find all feature sets matching a pattern and versions in a specific project - featureSets = - featureSetRepository.findAllByNameLikeAndProject_NameOrderByNameAscVersionAsc( - name.replace('*', '%'), project); - - } else if (!name.contains("*") && version.equals("*")) { - // Find all versions of a specific feature set in a specific project + if (name.contains("*")) { + // Find all feature sets matching a pattern in a specific project featureSets = - featureSetRepository.findAllByNameLikeAndProject_NameOrderByNameAscVersionAsc( - name, project); - - } else if (version.equals("latest")) { - // Find the latest version of a feature set matching a specific pattern in a specific - // project - FeatureSet latestFeatureSet = - featureSetRepository.findFirstFeatureSetByNameLikeAndProject_NameOrderByVersionDesc( + featureSetRepository.findAllByNameLikeAndProject_NameOrderByNameAsc( name.replace('*', '%'), project); - featureSets.add(latestFeatureSet); - } else if (!name.contains("*") && StringUtils.isNumeric(version)) { - // Find a specific version of a feature set matching a specific name in a specific project - FeatureSet specificFeatureSet = - featureSetRepository.findFeatureSetByNameAndProject_NameAndVersion( - name, project, Integer.parseInt(version)); - featureSets.add(specificFeatureSet); - - } else { - throw new IllegalArgumentException( - String.format( - "Invalid listFeatureSetRequest. Version must be set to \"*\" if the project " - + "name and feature set name aren't set explicitly: \n%s", - filter.toString())); + } else if (!name.contains("*")) { + // Find a specific feature set in a specific project + FeatureSet featureSet = + featureSetRepository.findFeatureSetByNameAndProject_Name(name, project); + if (featureSet != null) { + featureSets.add(featureSet); + } } } else { throw new IllegalArgumentException( @@ -271,84 +231,83 @@ public ListStoresResponse listStores(ListStoresRequest.Filter filter) { } /** - * Creates or updates a feature set in the repository. If there is a change in the feature set - * schema, then the feature set version will be incremented. + * Creates or updates a feature set in the repository. * *

This function is idempotent. If no changes are detected in the incoming featureSet's schema, * this method will update the incoming featureSet spec with the latest version stored in the - * repository, and return that. + * repository, and return that. If project is not specified in the given featureSet, will assign + * the featureSet to the'default' project. * * @param newFeatureSet Feature set that will be created or updated. */ - public ApplyFeatureSetResponse applyFeatureSet(FeatureSetProto.FeatureSet newFeatureSet) { + public ApplyFeatureSetResponse applyFeatureSet(FeatureSetProto.FeatureSet newFeatureSet) + throws InvalidProtocolBufferException { + // Autofill default project if not specified + if (newFeatureSet.getSpec().getProject().isEmpty()) { + newFeatureSet = + newFeatureSet + .toBuilder() + .setSpec(newFeatureSet.getSpec().toBuilder().setProject(Project.DEFAULT_NAME).build()) + .build(); + } // Validate incoming feature set FeatureSetValidator.validateSpec(newFeatureSet); - // Ensure that the project already exists + // Find project or create new one if it does not exist String project_name = newFeatureSet.getSpec().getProject(); Project project = projectRepository .findById(newFeatureSet.getSpec().getProject()) - .orElseThrow( - () -> - new IllegalArgumentException( - String.format( - "Project name does not exist. Please create a project first: %s", - project_name))); - - // Ensure that the project is not archived + .orElse(new Project(project_name)); + + // Ensure that the project retrieved from repository is not archived if (project.isArchived()) { throw new IllegalArgumentException(String.format("Project is archived: %s", project_name)); } - // Retrieve all existing FeatureSet objects - List existingFeatureSets = - featureSetRepository.findAllByNameLikeAndProject_NameOrderByNameAscVersionAsc( - newFeatureSet.getSpec().getName(), project_name); - - if (existingFeatureSets.size() == 0) { - // Create new feature set since it doesn't exist + // Set source to default if not set in proto + if (newFeatureSet.getSpec().getSource() == SourceProto.Source.getDefaultInstance()) { newFeatureSet = newFeatureSet .toBuilder() - .setSpec(newFeatureSet.getSpec().toBuilder().setVersion(1)) + .setSpec( + newFeatureSet.getSpec().toBuilder().setSource(defaultSource.toProto()).build()) .build(); - } else { - // Retrieve the latest feature set if the name does exist - existingFeatureSets = Ordering.natural().reverse().sortedCopy(existingFeatureSets); - FeatureSet latest = existingFeatureSets.get(0); - FeatureSet featureSet = FeatureSet.fromProto(newFeatureSet); + } + + // Retrieve existing FeatureSet + FeatureSet featureSet = + featureSetRepository.findFeatureSetByNameAndProject_Name( + newFeatureSet.getSpec().getName(), project_name); + Status status; + if (featureSet == null) { + // Create new feature set since it doesn't exist + newFeatureSet = newFeatureSet.toBuilder().setSpec(newFeatureSet.getSpec()).build(); + featureSet = FeatureSet.fromProto(newFeatureSet); + status = Status.CREATED; + } else { // If the featureSet remains unchanged, we do nothing. - if (featureSet.equalTo(latest)) { + if (featureSet.toProto().getSpec().equals(newFeatureSet.getSpec())) { return ApplyFeatureSetResponse.newBuilder() - .setFeatureSet(latest.toProto()) + .setFeatureSet(featureSet.toProto()) .setStatus(Status.NO_CHANGE) .build(); } - // TODO: There is a race condition here with incrementing the version - newFeatureSet = - newFeatureSet - .toBuilder() - .setSpec(newFeatureSet.getSpec().toBuilder().setVersion(latest.getVersion() + 1)) - .build(); - } - - // Build a new FeatureSet object which includes the new properties - FeatureSet featureSet = FeatureSet.fromProto(newFeatureSet); - if (newFeatureSet.getSpec().getSource() == SourceProto.Source.getDefaultInstance()) { - featureSet.setSource(defaultSource); + featureSet.updateFromProto(newFeatureSet); + status = Status.UPDATED; } // Persist the FeatureSet object + featureSet.setStatus(FeatureSetStatus.STATUS_PENDING); project.addFeatureSet(featureSet); projectRepository.saveAndFlush(project); // Build ApplyFeatureSetResponse return ApplyFeatureSetResponse.newBuilder() .setFeatureSet(featureSet.toProto()) - .setStatus(Status.CREATED) + .setStatus(status) .build(); } @@ -366,7 +325,7 @@ public UpdateStoreResponse updateStore(UpdateStoreRequest updateStoreRequest) List subs = newStoreProto.getSubscriptionsList(); for (Subscription sub : subs) { // Ensure that all fields in a subscription contain values - if ((sub.getVersion().isEmpty() || sub.getName().isEmpty()) || sub.getProject().isEmpty()) { + if ((sub.getName().isEmpty()) || sub.getProject().isEmpty()) { throw new IllegalArgumentException( String.format("Missing parameter in subscription: %s", sub)); } diff --git a/core/src/main/java/feast/core/util/PackageUtil.java b/core/src/main/java/feast/core/util/PackageUtil.java index 20b2310644b..99c5d73ba78 100644 --- a/core/src/main/java/feast/core/util/PackageUtil.java +++ b/core/src/main/java/feast/core/util/PackageUtil.java @@ -44,9 +44,9 @@ public class PackageUtil { * points to the resource location. Note that the extraction process can take several minutes to * complete. * - *

One use case of this function is to detect the class path of resources to stage when - * using Dataflow runner. The resource URL however is in "jar:file:" format, which cannot be - * handled by default in Apache Beam. + *

One use case of this function is to detect the class path of resources to stage when using + * Dataflow runner. The resource URL however is in "jar:file:" format, which cannot be handled by + * default in Apache Beam. * *

    * 
diff --git a/core/src/main/java/feast/core/util/PipelineUtil.java b/core/src/main/java/feast/core/util/PipelineUtil.java
index ef1b50d8c9a..8a84caf672c 100644
--- a/core/src/main/java/feast/core/util/PipelineUtil.java
+++ b/core/src/main/java/feast/core/util/PipelineUtil.java
@@ -24,7 +24,9 @@
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.stream.Collectors;
 
 public class PipelineUtil {
 
@@ -45,12 +47,7 @@ public class PipelineUtil {
   public static List detectClassPathResourcesToStage(ClassLoader classLoader)
       throws IOException {
     if (!(classLoader instanceof URLClassLoader)) {
-      String message =
-          String.format(
-              "Unable to use ClassLoader to detect classpath elements. "
-                  + "Current ClassLoader is %s, only URLClassLoaders are supported.",
-              classLoader);
-      throw new IllegalArgumentException(message);
+      return getClasspathFiles();
     }
 
     List files = new ArrayList<>();
@@ -69,4 +66,10 @@ public static List detectClassPathResourcesToStage(ClassLoader classLoad
     }
     return files;
   }
+
+  private static List getClasspathFiles() {
+    return Arrays.stream(System.getProperty("java.class.path").split(File.pathSeparator))
+        .map(entry -> new File(entry).getPath())
+        .collect(Collectors.toList());
+  }
 }
diff --git a/core/src/main/java/feast/core/util/TypeConversion.java b/core/src/main/java/feast/core/util/TypeConversion.java
index e01a5511359..6ee990fc1c5 100644
--- a/core/src/main/java/feast/core/util/TypeConversion.java
+++ b/core/src/main/java/feast/core/util/TypeConversion.java
@@ -70,9 +70,6 @@ public static Map convertJsonStringToMap(String jsonString) {
    * @return json string corresponding to given map
    */
   public static String convertMapToJsonString(Map map) {
-    if (map.isEmpty()) {
-      return "{}";
-    }
     return gson.toJson(map);
   }
 
diff --git a/core/src/main/java/feast/core/validators/FeatureSetValidator.java b/core/src/main/java/feast/core/validators/FeatureSetValidator.java
index 213e3898d51..9cfd136b84d 100644
--- a/core/src/main/java/feast/core/validators/FeatureSetValidator.java
+++ b/core/src/main/java/feast/core/validators/FeatureSetValidator.java
@@ -19,14 +19,15 @@
 import static feast.core.validators.Matchers.checkValidCharacters;
 
 import com.google.common.collect.Sets;
-import feast.core.FeatureSetProto.EntitySpec;
-import feast.core.FeatureSetProto.FeatureSet;
-import feast.core.FeatureSetProto.FeatureSpec;
+import feast.proto.core.FeatureSetProto.EntitySpec;
+import feast.proto.core.FeatureSetProto.FeatureSet;
+import feast.proto.core.FeatureSetProto.FeatureSpec;
 import java.util.HashSet;
 import java.util.List;
 import java.util.stream.Collectors;
 
 public class FeatureSetValidator {
+
   public static void validateSpec(FeatureSet featureSet) {
     if (featureSet.getSpec().getProject().isEmpty()) {
       throw new IllegalArgumentException("Project name must be provided");
@@ -34,6 +35,9 @@ public static void validateSpec(FeatureSet featureSet) {
     if (featureSet.getSpec().getName().isEmpty()) {
       throw new IllegalArgumentException("Feature set name must be provided");
     }
+    if (featureSet.getSpec().getLabelsMap().containsKey("")) {
+      throw new IllegalArgumentException("Feature set label keys must not be empty");
+    }
 
     checkValidCharacters(featureSet.getSpec().getProject(), "project");
     checkValidCharacters(featureSet.getSpec().getName(), "name");
@@ -44,6 +48,9 @@ public static void validateSpec(FeatureSet featureSet) {
     }
     for (FeatureSpec featureSpec : featureSet.getSpec().getFeaturesList()) {
       checkValidCharacters(featureSpec.getName(), "features::name");
+      if (featureSpec.getLabelsMap().containsKey("")) {
+        throw new IllegalArgumentException("Feature label keys must not be empty");
+      }
     }
   }
 
diff --git a/core/src/main/java/feast/core/validators/OneOfStringValidator.java b/core/src/main/java/feast/core/validators/OneOfStringValidator.java
new file mode 100644
index 00000000000..6b84e44b01c
--- /dev/null
+++ b/core/src/main/java/feast/core/validators/OneOfStringValidator.java
@@ -0,0 +1,51 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright 2018-2020 The Feast Authors
+ *
+ * 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 feast.core.validators;
+
+import java.util.Arrays;
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+/** Validates whether a string value is found within a collection. */
+public class OneOfStringValidator implements ConstraintValidator {
+
+  /** Values that are permitted for a specific instance of this validator */
+  String[] allowedValues;
+
+  /**
+   * Initialize the OneOfStringValidator with a collection of allowed String values.
+   *
+   * @param constraintAnnotation
+   */
+  @Override
+  public void initialize(OneOfStrings constraintAnnotation) {
+    allowedValues = constraintAnnotation.value();
+  }
+
+  /**
+   * Validates whether a string value is found within the collection defined in the annotation.
+   *
+   * @param value String value that should be validated
+   * @param context Provides contextual data and operation when applying a given constraint
+   *     validator
+   * @return Boolean value indicating whether the string is found within the allowed values.
+   */
+  @Override
+  public boolean isValid(String value, ConstraintValidatorContext context) {
+    return Arrays.asList(allowedValues).contains(value);
+  }
+}
diff --git a/core/src/main/java/feast/core/validators/OneOfStrings.java b/core/src/main/java/feast/core/validators/OneOfStrings.java
new file mode 100644
index 00000000000..dba290438c8
--- /dev/null
+++ b/core/src/main/java/feast/core/validators/OneOfStrings.java
@@ -0,0 +1,49 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright 2018-2020 The Feast Authors
+ *
+ * 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 feast.core.validators;
+
+import java.lang.annotation.*;
+import javax.validation.Constraint;
+import javax.validation.Payload;
+
+/**
+ * Annotation for String "one of" validation. Allows for the definition of a collection through an
+ * annotation. The collection is used to test values defined in the object.
+ */
+@Target({
+  ElementType.METHOD,
+  ElementType.FIELD,
+  ElementType.ANNOTATION_TYPE,
+  ElementType.CONSTRUCTOR,
+  ElementType.PARAMETER
+})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Constraint(validatedBy = OneOfStringValidator.class)
+public @interface OneOfStrings {
+  /** @return Default error message that is returned if the incorrect value is set */
+  String message() default "Field value must be one of the following: {value}";
+
+  /** Allows for the specification of validation groups to which this constraint belongs. */
+  Class[] groups() default {};
+
+  /** An attribute payload that can be used to assign custom payload objects to a constraint. */
+  Class[] payload() default {};
+
+  /** @return Default value that is returned if no allowed values are configured */
+  String[] value() default {};
+}
diff --git a/core/src/main/proto/feast b/core/src/main/proto/feast
deleted file mode 120000
index d520da9126b..00000000000
--- a/core/src/main/proto/feast
+++ /dev/null
@@ -1 +0,0 @@
-../../../../protos/feast
\ No newline at end of file
diff --git a/core/src/main/proto/third_party b/core/src/main/proto/third_party
deleted file mode 120000
index 363d20598e6..00000000000
--- a/core/src/main/proto/third_party
+++ /dev/null
@@ -1 +0,0 @@
-../../../../protos/third_party
\ No newline at end of file
diff --git a/core/src/main/resources/application.yml b/core/src/main/resources/application.yml
index dc78719f22e..51395cf6449 100644
--- a/core/src/main/resources/application.yml
+++ b/core/src/main/resources/application.yml
@@ -23,16 +23,39 @@ grpc:
   enable-reflection: true
 
 feast:
-#  version: @project.version@
   jobs:
-    # Runner type for feature population jobs. Currently supported runner types are
-    # DirectRunner and DataflowRunner.
-    runner: DirectRunner
-    # Key-value dict of job options to be passed to the population jobs.
-    options: {}
-    updates:
-      # Timeout in seconds for each attempt to update or submit a new job to the runner.
-      timeoutSeconds: 240
+    # Job update polling interval in milliseconds: how often Feast checks if new jobs should be sent to the runner.
+    polling_interval_milliseconds: 60000
+
+    # Timeout in seconds for each attempt to update or submit a new job to the runner.
+    job_update_timeout_seconds: 240
+
+    # Name of the active runner in "runners" that should be used. Only a single runner can be active at one time.
+    active_runner: direct
+
+    # List of runner configurations. Please see protos/feast/core/Runner.proto for more details
+    # Alternatively see the following for options https://api.docs.feast.dev/grpc/feast.core.pb.html#Runner
+    runners:
+      - name: direct
+        type: DirectRunner
+        options: {}
+
+      - name: dataflow
+        type: DataflowRunner
+        options:
+          project: my_gcp_project
+          region: asia-east1
+          zone: asia-east1-a
+          tempLocation: gs://bucket/tempLocation
+          network: default
+          subnetwork: regions/asia-east1/subnetworks/mysubnetwork
+          maxNumWorkers: 1
+          autoscalingAlgorithm: THROUGHPUT_BASED
+          usePublicIps: false
+          workerMachineType: n1-standard-1
+          deadLetterTableSpec: project_id:dataset_id.table_id
+
+    # Configuration options for metric collection for all ingestion jobs
     metrics:
       # Enable metrics pushing for all ingestion jobs.
       enabled: false
@@ -47,9 +70,10 @@ feast:
     # Feature stream type. Only kafka is supported.
     type: kafka
     # Feature stream options.
+    # See the following for options https://api.docs.feast.dev/grpc/feast.core.pb.html#KafkaSourceConfig
     options:
       topic: feast-features
-      bootstrapServers: kafka:9092
+      bootstrapServers: localhost:9092
       replicationFactor: 1
       partitions: 1
 
diff --git a/core/src/main/resources/log4j2.xml b/core/src/main/resources/log4j2.xml
index 65b3c5aa4bb..efbf7d1f624 100644
--- a/core/src/main/resources/log4j2.xml
+++ b/core/src/main/resources/log4j2.xml
@@ -22,9 +22,10 @@
             %d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${hostName} --- [%15.15t] %-40.40c{1.} : %m%n%ex
         
         ${env:LOG_TYPE:-Console}
+        ${env:LOG_LEVEL:-info}
     
     
-        
+        
     
     
         
@@ -35,8 +36,11 @@
         
     
     
-        
-            
-        
+      
+        
+      
+      
+        
+      
     
 
diff --git a/core/src/test/java/feast/core/http/HealthControllerTest.java b/core/src/test/java/feast/core/http/HealthControllerTest.java
deleted file mode 100644
index 2fcd622f34a..00000000000
--- a/core/src/test/java/feast/core/http/HealthControllerTest.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * SPDX-License-Identifier: Apache-2.0
- * Copyright 2018-2019 The Feast Authors
- *
- * 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 feast.core.http;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.*;
-
-import java.sql.Connection;
-import java.sql.SQLException;
-import javax.sql.DataSource;
-import org.junit.Test;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
-
-public class HealthControllerTest {
-  @Test
-  public void ping() {
-    HealthController healthController = new HealthController(null);
-    assertEquals(ResponseEntity.ok("pong"), healthController.ping());
-  }
-
-  @Test
-  public void healthz() {
-    assertEquals(ResponseEntity.ok("healthy"), mockHealthyController().healthz());
-    assertEquals(
-        ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
-            .body("Unable to establish connection with DB"),
-        mockUnhealthyControllerBecauseInvalidConn().healthz());
-    assertEquals(
-        ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("mocked sqlexception"),
-        mockUnhealthyControllerBecauseSQLException().healthz());
-  }
-
-  private HealthController mockHealthyController() {
-    DataSource mockDataSource = mock(DataSource.class);
-    Connection mockConnection = mock(Connection.class);
-    try {
-      when(mockConnection.isValid(any(int.class))).thenReturn(Boolean.TRUE);
-      when(mockDataSource.getConnection()).thenReturn(mockConnection);
-    } catch (Exception e) {
-      e.printStackTrace();
-    }
-    return new HealthController(mockDataSource);
-  }
-
-  private HealthController mockUnhealthyControllerBecauseInvalidConn() {
-    DataSource mockDataSource = mock(DataSource.class);
-    Connection mockConnection = mock(Connection.class);
-    try {
-      when(mockConnection.isValid(any(int.class))).thenReturn(Boolean.FALSE);
-      when(mockDataSource.getConnection()).thenReturn(mockConnection);
-    } catch (Exception ignored) {
-    }
-    return new HealthController(mockDataSource);
-  }
-
-  private HealthController mockUnhealthyControllerBecauseSQLException() {
-    DataSource mockDataSource = mock(DataSource.class);
-    Connection mockConnection = mock(Connection.class);
-    try {
-      when(mockDataSource.getConnection()).thenThrow(new SQLException("mocked sqlexception"));
-    } catch (SQLException ignored) {
-    }
-    return new HealthController(mockDataSource);
-  }
-}
diff --git a/core/src/test/java/feast/core/job/JobUpdateTaskTest.java b/core/src/test/java/feast/core/job/JobUpdateTaskTest.java
index 19ce0858b20..d1826738019 100644
--- a/core/src/test/java/feast/core/job/JobUpdateTaskTest.java
+++ b/core/src/test/java/feast/core/job/JobUpdateTaskTest.java
@@ -24,22 +24,24 @@
 import static org.mockito.Mockito.when;
 import static org.mockito.MockitoAnnotations.initMocks;
 
-import feast.core.FeatureSetProto;
-import feast.core.FeatureSetProto.FeatureSetMeta;
-import feast.core.FeatureSetProto.FeatureSetSpec;
-import feast.core.SourceProto;
-import feast.core.SourceProto.KafkaSourceConfig;
-import feast.core.SourceProto.SourceType;
-import feast.core.StoreProto;
-import feast.core.StoreProto.Store.RedisConfig;
-import feast.core.StoreProto.Store.StoreType;
-import feast.core.StoreProto.Store.Subscription;
 import feast.core.model.FeatureSet;
 import feast.core.model.Job;
 import feast.core.model.JobStatus;
 import feast.core.model.Source;
 import feast.core.model.Store;
+import feast.proto.core.FeatureSetProto;
+import feast.proto.core.FeatureSetProto.FeatureSetMeta;
+import feast.proto.core.FeatureSetProto.FeatureSetSpec;
+import feast.proto.core.SourceProto;
+import feast.proto.core.SourceProto.KafkaSourceConfig;
+import feast.proto.core.SourceProto.SourceType;
+import feast.proto.core.StoreProto;
+import feast.proto.core.StoreProto.Store.RedisConfig;
+import feast.proto.core.StoreProto.Store.StoreType;
+import feast.proto.core.StoreProto.Store.Subscription;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 import java.util.Optional;
 import org.hamcrest.core.IsNull;
 import org.junit.Before;
@@ -47,95 +49,69 @@
 import org.mockito.Mock;
 
 public class JobUpdateTaskTest {
+  private static final Runner RUNNER = Runner.DATAFLOW;
+
+  private static final FeatureSetProto.FeatureSet.Builder fsBuilder =
+      FeatureSetProto.FeatureSet.newBuilder().setMeta(FeatureSetMeta.newBuilder());
+  private static final FeatureSetSpec.Builder specBuilder = FeatureSetSpec.newBuilder();
 
   @Mock private JobManager jobManager;
 
-  private StoreProto.Store store;
-  private SourceProto.Source source;
+  private Store store;
+  private Source source;
+  private FeatureSet featureSet1;
 
   @Before
   public void setUp() {
     initMocks(this);
+    when(jobManager.getRunnerType()).thenReturn(RUNNER);
+
     store =
-        StoreProto.Store.newBuilder()
-            .setName("test")
-            .setType(StoreType.REDIS)
-            .setRedisConfig(RedisConfig.newBuilder().build())
-            .addSubscriptions(
-                Subscription.newBuilder().setProject("*").setName("*").setVersion("*").build())
-            .build();
+        Store.fromProto(
+            StoreProto.Store.newBuilder()
+                .setName("test")
+                .setType(StoreType.REDIS)
+                .setRedisConfig(RedisConfig.newBuilder().build())
+                .addSubscriptions(Subscription.newBuilder().setProject("*").setName("*").build())
+                .build());
 
     source =
-        SourceProto.Source.newBuilder()
-            .setType(SourceType.KAFKA)
-            .setKafkaSourceConfig(
-                KafkaSourceConfig.newBuilder()
-                    .setTopic("topic")
-                    .setBootstrapServers("servers:9092")
-                    .build())
-            .build();
+        Source.fromProto(
+            SourceProto.Source.newBuilder()
+                .setType(SourceType.KAFKA)
+                .setKafkaSourceConfig(
+                    KafkaSourceConfig.newBuilder()
+                        .setTopic("topic")
+                        .setBootstrapServers("servers:9092")
+                        .build())
+                .build());
+
+    featureSet1 =
+        FeatureSet.fromProto(fsBuilder.setSpec(specBuilder.setName("featureSet1")).build());
+    featureSet1.setSource(source);
+  }
+
+  Job makeJob(String extId, List featureSets, JobStatus status) {
+    return new Job("job", extId, RUNNER, source, store, featureSets, status);
+  }
+
+  JobUpdateTask makeTask(List featureSets, Optional currentJob) {
+    return new JobUpdateTask(featureSets, source, store, currentJob, jobManager, 100L);
   }
 
   @Test
   public void shouldUpdateJobIfPresent() {
-    FeatureSetProto.FeatureSet featureSet1 =
-        FeatureSetProto.FeatureSet.newBuilder()
-            .setSpec(
-                FeatureSetSpec.newBuilder()
-                    .setSource(source)
-                    .setProject("project1")
-                    .setName("featureSet1")
-                    .setVersion(1))
-            .setMeta(FeatureSetMeta.newBuilder())
-            .build();
-    FeatureSetProto.FeatureSet featureSet2 =
-        FeatureSetProto.FeatureSet.newBuilder()
-            .setSpec(
-                FeatureSetSpec.newBuilder()
-                    .setSource(source)
-                    .setProject("project1")
-                    .setName("featureSet2")
-                    .setVersion(1))
-            .setMeta(FeatureSetMeta.newBuilder())
-            .build();
-    Job originalJob =
-        new Job(
-            "job",
-            "old_ext",
-            Runner.DATAFLOW.getName(),
-            feast.core.model.Source.fromProto(source),
-            feast.core.model.Store.fromProto(store),
-            Arrays.asList(FeatureSet.fromProto(featureSet1)),
-            JobStatus.RUNNING);
-    JobUpdateTask jobUpdateTask =
-        new JobUpdateTask(
-            Arrays.asList(featureSet1, featureSet2),
-            source,
-            store,
-            Optional.of(originalJob),
-            jobManager,
-            100L);
-    Job submittedJob =
-        new Job(
-            "job",
-            "old_ext",
-            Runner.DATAFLOW.getName(),
-            feast.core.model.Source.fromProto(source),
-            feast.core.model.Store.fromProto(store),
-            Arrays.asList(FeatureSet.fromProto(featureSet1), FeatureSet.fromProto(featureSet2)),
-            JobStatus.RUNNING);
+    FeatureSet featureSet2 =
+        FeatureSet.fromProto(fsBuilder.setSpec(specBuilder.setName("featureSet2")).build());
+    List existingFeatureSetsPopulatedByJob = Collections.singletonList(featureSet1);
+    List newFeatureSetsPopulatedByJob = Arrays.asList(featureSet1, featureSet2);
+
+    Job originalJob = makeJob("old_ext", existingFeatureSetsPopulatedByJob, JobStatus.RUNNING);
+    JobUpdateTask jobUpdateTask = makeTask(newFeatureSetsPopulatedByJob, Optional.of(originalJob));
+    Job submittedJob = makeJob("old_ext", newFeatureSetsPopulatedByJob, JobStatus.RUNNING);
 
-    Job expected =
-        new Job(
-            "job",
-            "new_ext",
-            Runner.DATAFLOW.getName(),
-            Source.fromProto(source),
-            Store.fromProto(store),
-            Arrays.asList(FeatureSet.fromProto(featureSet1), FeatureSet.fromProto(featureSet2)),
-            JobStatus.PENDING);
+    Job expected = makeJob("new_ext", newFeatureSetsPopulatedByJob, JobStatus.PENDING);
     when(jobManager.updateJob(submittedJob)).thenReturn(expected);
-    when(jobManager.getRunnerType()).thenReturn(Runner.DATAFLOW);
     Job actual = jobUpdateTask.call();
 
     assertThat(actual, equalTo(expected));
@@ -143,43 +119,13 @@ public void shouldUpdateJobIfPresent() {
 
   @Test
   public void shouldCreateJobIfNotPresent() {
-    FeatureSetProto.FeatureSet featureSet1 =
-        FeatureSetProto.FeatureSet.newBuilder()
-            .setSpec(
-                FeatureSetSpec.newBuilder()
-                    .setSource(source)
-                    .setProject("project1")
-                    .setName("featureSet1")
-                    .setVersion(1))
-            .setMeta(FeatureSetMeta.newBuilder())
-            .build();
-    JobUpdateTask jobUpdateTask =
-        spy(
-            new JobUpdateTask(
-                Arrays.asList(featureSet1), source, store, Optional.empty(), jobManager, 100L));
+    var featureSets = Collections.singletonList(featureSet1);
+    JobUpdateTask jobUpdateTask = spy(makeTask(featureSets, Optional.empty()));
     doReturn("job").when(jobUpdateTask).createJobId("KAFKA/servers:9092/topic", "test");
 
-    Job expectedInput =
-        new Job(
-            "job",
-            "",
-            Runner.DATAFLOW.getName(),
-            feast.core.model.Source.fromProto(source),
-            feast.core.model.Store.fromProto(store),
-            Arrays.asList(FeatureSet.fromProto(featureSet1)),
-            JobStatus.PENDING);
+    Job expectedInput = makeJob("", featureSets, JobStatus.PENDING);
+    Job expected = makeJob("ext", featureSets, JobStatus.PENDING);
 
-    Job expected =
-        new Job(
-            "job",
-            "ext",
-            Runner.DATAFLOW.getName(),
-            feast.core.model.Source.fromProto(source),
-            feast.core.model.Store.fromProto(store),
-            Arrays.asList(FeatureSet.fromProto(featureSet1)),
-            JobStatus.RUNNING);
-
-    when(jobManager.getRunnerType()).thenReturn(Runner.DATAFLOW);
     when(jobManager.startJob(expectedInput)).thenReturn(expected);
 
     Job actual = jobUpdateTask.call();
@@ -188,83 +134,25 @@ public void shouldCreateJobIfNotPresent() {
 
   @Test
   public void shouldUpdateJobStatusIfNotCreateOrUpdate() {
-    FeatureSetProto.FeatureSet featureSet1 =
-        FeatureSetProto.FeatureSet.newBuilder()
-            .setSpec(
-                FeatureSetSpec.newBuilder()
-                    .setSource(source)
-                    .setProject("project1")
-                    .setName("featureSet1")
-                    .setVersion(1))
-            .setMeta(FeatureSetMeta.newBuilder())
-            .build();
-    Job originalJob =
-        new Job(
-            "job",
-            "ext",
-            Runner.DATAFLOW.getName(),
-            feast.core.model.Source.fromProto(source),
-            feast.core.model.Store.fromProto(store),
-            Arrays.asList(FeatureSet.fromProto(featureSet1)),
-            JobStatus.RUNNING);
-    JobUpdateTask jobUpdateTask =
-        new JobUpdateTask(
-            Arrays.asList(featureSet1), source, store, Optional.of(originalJob), jobManager, 100L);
+    var featureSets = Collections.singletonList(featureSet1);
+    Job originalJob = makeJob("ext", featureSets, JobStatus.RUNNING);
+    JobUpdateTask jobUpdateTask = makeTask(featureSets, Optional.of(originalJob));
 
     when(jobManager.getJobStatus(originalJob)).thenReturn(JobStatus.ABORTING);
-    Job expected =
-        new Job(
-            "job",
-            "ext",
-            Runner.DATAFLOW.getName(),
-            Source.fromProto(source),
-            Store.fromProto(store),
-            Arrays.asList(FeatureSet.fromProto(featureSet1)),
-            JobStatus.ABORTING);
-    Job actual = jobUpdateTask.call();
+    Job updated = jobUpdateTask.call();
 
-    assertThat(actual, equalTo(expected));
+    assertThat(updated.getStatus(), equalTo(JobStatus.ABORTING));
   }
 
   @Test
   public void shouldReturnJobWithErrorStatusIfFailedToSubmit() {
-    FeatureSetProto.FeatureSet featureSet1 =
-        FeatureSetProto.FeatureSet.newBuilder()
-            .setSpec(
-                FeatureSetSpec.newBuilder()
-                    .setSource(source)
-                    .setProject("project1")
-                    .setName("featureSet1")
-                    .setVersion(1))
-            .setMeta(FeatureSetMeta.newBuilder())
-            .build();
-    JobUpdateTask jobUpdateTask =
-        spy(
-            new JobUpdateTask(
-                Arrays.asList(featureSet1), source, store, Optional.empty(), jobManager, 100L));
+    var featureSets = Collections.singletonList(featureSet1);
+    JobUpdateTask jobUpdateTask = spy(makeTask(featureSets, Optional.empty()));
     doReturn("job").when(jobUpdateTask).createJobId("KAFKA/servers:9092/topic", "test");
 
-    Job expectedInput =
-        new Job(
-            "job",
-            "",
-            Runner.DATAFLOW.getName(),
-            feast.core.model.Source.fromProto(source),
-            feast.core.model.Store.fromProto(store),
-            Arrays.asList(FeatureSet.fromProto(featureSet1)),
-            JobStatus.PENDING);
-
-    Job expected =
-        new Job(
-            "job",
-            "",
-            Runner.DATAFLOW.getName(),
-            feast.core.model.Source.fromProto(source),
-            feast.core.model.Store.fromProto(store),
-            Arrays.asList(FeatureSet.fromProto(featureSet1)),
-            JobStatus.ERROR);
+    Job expectedInput = makeJob("", featureSets, JobStatus.PENDING);
+    Job expected = makeJob("", featureSets, JobStatus.ERROR);
 
-    when(jobManager.getRunnerType()).thenReturn(Runner.DATAFLOW);
     when(jobManager.startJob(expectedInput))
         .thenThrow(new RuntimeException("Something went wrong"));
 
@@ -274,21 +162,13 @@ public void shouldReturnJobWithErrorStatusIfFailedToSubmit() {
 
   @Test
   public void shouldTimeout() {
-    FeatureSetProto.FeatureSet featureSet1 =
-        FeatureSetProto.FeatureSet.newBuilder()
-            .setSpec(
-                FeatureSetSpec.newBuilder()
-                    .setSource(source)
-                    .setProject("project1")
-                    .setName("featureSet1")
-                    .setVersion(1))
-            .setMeta(FeatureSetMeta.newBuilder())
-            .build();
-
+    var featureSets = Collections.singletonList(featureSet1);
+    var timeoutSeconds = 0L;
     JobUpdateTask jobUpdateTask =
         spy(
             new JobUpdateTask(
-                Arrays.asList(featureSet1), source, store, Optional.empty(), jobManager, 0L));
+                featureSets, source, store, Optional.empty(), jobManager, timeoutSeconds));
+
     Job actual = jobUpdateTask.call();
     assertThat(actual, is(IsNull.nullValue()));
   }
diff --git a/core/src/test/java/feast/core/job/RunnerTest.java b/core/src/test/java/feast/core/job/RunnerTest.java
new file mode 100644
index 00000000000..ce1700acbe9
--- /dev/null
+++ b/core/src/test/java/feast/core/job/RunnerTest.java
@@ -0,0 +1,42 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright 2018-2020 The Feast Authors
+ *
+ * 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 feast.core.job;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.util.NoSuchElementException;
+import org.junit.Test;
+
+public class RunnerTest {
+
+  @Test
+  public void toStringReturnsHumanReadableName() {
+    assertThat(Runner.DATAFLOW.toString(), is("DataflowRunner"));
+  }
+
+  @Test
+  public void fromStringLoadsValueFromHumanReadableName() {
+    var humanName = Runner.DATAFLOW.toString();
+    assertThat(Runner.fromString(humanName), is(Runner.DATAFLOW));
+  }
+
+  @Test(expected = NoSuchElementException.class)
+  public void fromStringThrowsNoSuchElementExceptionForUnknownValue() {
+    Runner.fromString("this is not a valid Runner");
+  }
+}
diff --git a/core/src/test/java/feast/core/job/dataflow/DataflowJobManagerTest.java b/core/src/test/java/feast/core/job/dataflow/DataflowJobManagerTest.java
index c263515ed08..632c9d22a25 100644
--- a/core/src/test/java/feast/core/job/dataflow/DataflowJobManagerTest.java
+++ b/core/src/test/java/feast/core/job/dataflow/DataflowJobManagerTest.java
@@ -19,39 +19,38 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.equalTo;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
 import static org.mockito.MockitoAnnotations.initMocks;
 
+import com.google.api.client.auth.oauth2.Credential;
+import com.google.api.client.googleapis.testing.auth.oauth2.MockGoogleCredential;
 import com.google.api.services.dataflow.Dataflow;
 import com.google.common.collect.Lists;
 import com.google.protobuf.Duration;
 import com.google.protobuf.util.JsonFormat;
 import com.google.protobuf.util.JsonFormat.Printer;
-import feast.core.FeatureSetProto;
-import feast.core.FeatureSetProto.FeatureSetMeta;
-import feast.core.FeatureSetProto.FeatureSetSpec;
-import feast.core.SourceProto;
-import feast.core.SourceProto.KafkaSourceConfig;
-import feast.core.SourceProto.SourceType;
-import feast.core.StoreProto;
-import feast.core.StoreProto.Store.RedisConfig;
-import feast.core.StoreProto.Store.StoreType;
-import feast.core.StoreProto.Store.Subscription;
 import feast.core.config.FeastProperties.MetricsProperties;
 import feast.core.exception.JobExecutionException;
 import feast.core.job.Runner;
-import feast.core.model.FeatureSet;
-import feast.core.model.Job;
-import feast.core.model.JobStatus;
-import feast.core.model.Source;
-import feast.core.model.Store;
+import feast.core.job.option.FeatureSetJsonByteConverter;
+import feast.core.model.*;
+import feast.ingestion.options.BZip2Compressor;
 import feast.ingestion.options.ImportOptions;
+import feast.ingestion.options.OptionCompressor;
+import feast.proto.core.FeatureSetProto;
+import feast.proto.core.FeatureSetProto.FeatureSetMeta;
+import feast.proto.core.FeatureSetProto.FeatureSetSpec;
+import feast.proto.core.SourceProto;
+import feast.proto.core.SourceProto.KafkaSourceConfig;
+import feast.proto.core.SourceProto.SourceType;
+import feast.proto.core.StoreProto;
+import feast.proto.core.StoreProto.Store.RedisConfig;
+import feast.proto.core.StoreProto.Store.StoreType;
+import feast.proto.core.StoreProto.Store.Subscription;
 import java.io.IOException;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import org.apache.beam.runners.dataflow.DataflowPipelineJob;
 import org.apache.beam.runners.dataflow.DataflowRunner;
@@ -80,9 +79,20 @@ public void setUp() {
     defaults = new HashMap<>();
     defaults.put("project", "project");
     defaults.put("region", "region");
+    defaults.put("zone", "zone");
+    defaults.put("tempLocation", "tempLocation");
+    defaults.put("network", "network");
+    defaults.put("subnetwork", "subnetwork");
     MetricsProperties metricsProperties = new MetricsProperties();
     metricsProperties.setEnabled(false);
-    dfJobManager = new DataflowJobManager(dataflow, defaults, metricsProperties);
+    Credential credential = null;
+    try {
+      credential = MockGoogleCredential.getApplicationDefault();
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+
+    dfJobManager = new DataflowJobManager(defaults, metricsProperties, credential);
     dfJobManager = spy(dfJobManager);
   }
 
@@ -93,8 +103,7 @@ public void shouldStartJobWithCorrectPipelineOptions() throws IOException {
             .setName("SERVING")
             .setType(StoreType.REDIS)
             .setRedisConfig(RedisConfig.newBuilder().setHost("localhost").setPort(6379).build())
-            .addSubscriptions(
-                Subscription.newBuilder().setProject("*").setName("*").setVersion("*").build())
+            .addSubscriptions(Subscription.newBuilder().setProject("*").setName("*").build())
             .build();
 
     SourceProto.Source source =
@@ -114,7 +123,6 @@ public void shouldStartJobWithCorrectPipelineOptions() throws IOException {
                 FeatureSetSpec.newBuilder()
                     .setSource(source)
                     .setName("featureSet")
-                    .setVersion(1)
                     .setMaxAge(Duration.newBuilder().build()))
             .build();
 
@@ -131,8 +139,11 @@ public void shouldStartJobWithCorrectPipelineOptions() throws IOException {
     expectedPipelineOptions.setAppName("DataflowJobManager");
     expectedPipelineOptions.setJobName(jobName);
     expectedPipelineOptions.setStoreJson(Lists.newArrayList(printer.print(store)));
+
+    OptionCompressor> featureSetJsonCompressor =
+        new BZip2Compressor<>(new FeatureSetJsonByteConverter());
     expectedPipelineOptions.setFeatureSetJson(
-        Lists.newArrayList(printer.print(featureSet.getSpec())));
+        featureSetJsonCompressor.compress(Collections.singletonList(featureSet)));
 
     ArgumentCaptor captor = ArgumentCaptor.forClass(ImportOptions.class);
 
@@ -145,7 +156,7 @@ public void shouldStartJobWithCorrectPipelineOptions() throws IOException {
         new Job(
             jobName,
             "",
-            Runner.DATAFLOW.getName(),
+            Runner.DATAFLOW,
             Source.fromProto(source),
             Store.fromProto(store),
             Lists.newArrayList(FeatureSet.fromProto(featureSet)),
@@ -159,7 +170,7 @@ public void shouldStartJobWithCorrectPipelineOptions() throws IOException {
         actualPipelineOptions.getOptionsId()); // avoid comparing this value
 
     // We only check that we are calling getFilesToStage() manually, because the automatic approach
-    // throws an error: https://github.com/gojek/feast/pull/291 i.e. do not check for the actual
+    // throws an error: https://github.com/feast-dev/feast/pull/291 i.e. do not check for the actual
     // files that are staged
     assertThat(
         "filesToStage in pipelineOptions should not be null, job manager should set it.",
@@ -170,7 +181,19 @@ public void shouldStartJobWithCorrectPipelineOptions() throws IOException {
     // Assume the files that are staged are correct
     expectedPipelineOptions.setFilesToStage(actualPipelineOptions.getFilesToStage());
 
-    assertThat(actualPipelineOptions.toString(), equalTo(expectedPipelineOptions.toString()));
+    assertThat(
+        actualPipelineOptions.getFeatureSetJson(),
+        equalTo(expectedPipelineOptions.getFeatureSetJson()));
+    assertThat(
+        actualPipelineOptions.getDeadLetterTableSpec(),
+        equalTo(expectedPipelineOptions.getDeadLetterTableSpec()));
+    assertThat(
+        actualPipelineOptions.getStatsdHost(), equalTo(expectedPipelineOptions.getStatsdHost()));
+    assertThat(
+        actualPipelineOptions.getMetricsExporterType(),
+        equalTo(expectedPipelineOptions.getMetricsExporterType()));
+    assertThat(
+        actualPipelineOptions.getStoreJson(), equalTo(expectedPipelineOptions.getStoreJson()));
     assertThat(actual.getExtId(), equalTo(expectedExtJobId));
   }
 
@@ -195,12 +218,7 @@ public void shouldThrowExceptionWhenJobStateTerminal() throws IOException {
 
     FeatureSetProto.FeatureSet featureSet =
         FeatureSetProto.FeatureSet.newBuilder()
-            .setSpec(
-                FeatureSetSpec.newBuilder()
-                    .setName("featureSet")
-                    .setVersion(1)
-                    .setSource(source)
-                    .build())
+            .setSpec(FeatureSetSpec.newBuilder().setName("featureSet").setSource(source).build())
             .build();
 
     dfJobManager = Mockito.spy(dfJobManager);
@@ -214,7 +232,7 @@ public void shouldThrowExceptionWhenJobStateTerminal() throws IOException {
         new Job(
             "job",
             "",
-            Runner.DATAFLOW.getName(),
+            Runner.DATAFLOW,
             Source.fromProto(source),
             Store.fromProto(store),
             Lists.newArrayList(FeatureSet.fromProto(featureSet)),
diff --git a/core/src/test/java/feast/core/job/direct/DirectRunnerJobManagerTest.java b/core/src/test/java/feast/core/job/direct/DirectRunnerJobManagerTest.java
index 2dd87cfc6e3..42d3189a730 100644
--- a/core/src/test/java/feast/core/job/direct/DirectRunnerJobManagerTest.java
+++ b/core/src/test/java/feast/core/job/direct/DirectRunnerJobManagerTest.java
@@ -29,25 +29,30 @@
 import com.google.protobuf.Duration;
 import com.google.protobuf.util.JsonFormat;
 import com.google.protobuf.util.JsonFormat.Printer;
-import feast.core.FeatureSetProto;
-import feast.core.FeatureSetProto.FeatureSetSpec;
-import feast.core.SourceProto;
-import feast.core.SourceProto.KafkaSourceConfig;
-import feast.core.SourceProto.SourceType;
-import feast.core.StoreProto;
-import feast.core.StoreProto.Store.RedisConfig;
-import feast.core.StoreProto.Store.StoreType;
-import feast.core.StoreProto.Store.Subscription;
 import feast.core.config.FeastProperties.MetricsProperties;
 import feast.core.job.Runner;
+import feast.core.job.option.FeatureSetJsonByteConverter;
 import feast.core.model.FeatureSet;
 import feast.core.model.Job;
 import feast.core.model.JobStatus;
 import feast.core.model.Source;
 import feast.core.model.Store;
+import feast.ingestion.options.BZip2Compressor;
 import feast.ingestion.options.ImportOptions;
+import feast.ingestion.options.OptionCompressor;
+import feast.proto.core.FeatureSetProto;
+import feast.proto.core.FeatureSetProto.FeatureSetSpec;
+import feast.proto.core.SourceProto;
+import feast.proto.core.SourceProto.KafkaSourceConfig;
+import feast.proto.core.SourceProto.SourceType;
+import feast.proto.core.StoreProto;
+import feast.proto.core.StoreProto.Store.RedisConfig;
+import feast.proto.core.StoreProto.Store.StoreType;
+import feast.proto.core.StoreProto.Store.Subscription;
 import java.io.IOException;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import org.apache.beam.runners.direct.DirectRunner;
 import org.apache.beam.sdk.PipelineResult;
@@ -86,8 +91,7 @@ public void shouldStartDirectJobAndRegisterPipelineResult() throws IOException {
             .setName("SERVING")
             .setType(StoreType.REDIS)
             .setRedisConfig(RedisConfig.newBuilder().setHost("localhost").setPort(6379).build())
-            .addSubscriptions(
-                Subscription.newBuilder().setProject("*").setName("*").setVersion("*").build())
+            .addSubscriptions(Subscription.newBuilder().setProject("*").setName("*").build())
             .build();
 
     SourceProto.Source source =
@@ -105,7 +109,6 @@ public void shouldStartDirectJobAndRegisterPipelineResult() throws IOException {
             .setSpec(
                 FeatureSetSpec.newBuilder()
                     .setName("featureSet")
-                    .setVersion(1)
                     .setMaxAge(Duration.newBuilder())
                     .setSource(source)
                     .build())
@@ -113,18 +116,22 @@ public void shouldStartDirectJobAndRegisterPipelineResult() throws IOException {
 
     Printer printer = JsonFormat.printer();
 
+    String expectedJobId = "feast-job-0";
     ImportOptions expectedPipelineOptions =
         PipelineOptionsFactory.fromArgs("").as(ImportOptions.class);
+    expectedPipelineOptions.setJobName(expectedJobId);
     expectedPipelineOptions.setAppName("DirectRunnerJobManager");
     expectedPipelineOptions.setRunner(DirectRunner.class);
     expectedPipelineOptions.setBlockOnRun(false);
     expectedPipelineOptions.setProject("");
     expectedPipelineOptions.setStoreJson(Lists.newArrayList(printer.print(store)));
     expectedPipelineOptions.setProject("");
+
+    OptionCompressor> featureSetJsonCompressor =
+        new BZip2Compressor<>(new FeatureSetJsonByteConverter());
     expectedPipelineOptions.setFeatureSetJson(
-        Lists.newArrayList(printer.print(featureSet.getSpec())));
+        featureSetJsonCompressor.compress(Collections.singletonList(featureSet)));
 
-    String expectedJobId = "feast-job-0";
     ArgumentCaptor pipelineOptionsCaptor =
         ArgumentCaptor.forClass(ImportOptions.class);
     ArgumentCaptor directJobCaptor = ArgumentCaptor.forClass(DirectJob.class);
@@ -136,7 +143,7 @@ public void shouldStartDirectJobAndRegisterPipelineResult() throws IOException {
         new Job(
             expectedJobId,
             "",
-            Runner.DIRECT.getName(),
+            Runner.DIRECT,
             Source.fromProto(source),
             Store.fromProto(store),
             Lists.newArrayList(FeatureSet.fromProto(featureSet)),
@@ -150,7 +157,20 @@ public void shouldStartDirectJobAndRegisterPipelineResult() throws IOException {
     expectedPipelineOptions.setOptionsId(
         actualPipelineOptions.getOptionsId()); // avoid comparing this value
 
-    assertThat(actualPipelineOptions.toString(), equalTo(expectedPipelineOptions.toString()));
+    assertThat(
+        actualPipelineOptions.getFeatureSetJson(),
+        equalTo(expectedPipelineOptions.getFeatureSetJson()));
+    assertThat(
+        actualPipelineOptions.getDeadLetterTableSpec(),
+        equalTo(expectedPipelineOptions.getDeadLetterTableSpec()));
+    assertThat(
+        actualPipelineOptions.getStatsdHost(), equalTo(expectedPipelineOptions.getStatsdHost()));
+    assertThat(
+        actualPipelineOptions.getMetricsExporterType(),
+        equalTo(expectedPipelineOptions.getMetricsExporterType()));
+    assertThat(
+        actualPipelineOptions.getStoreJson(), equalTo(expectedPipelineOptions.getStoreJson()));
+
     assertThat(jobStarted.getPipelineResult(), equalTo(mockPipelineResult));
     assertThat(jobStarted.getJobId(), equalTo(expectedJobId));
     assertThat(actual.getExtId(), equalTo(expectedJobId));
diff --git a/core/src/test/java/feast/core/job/option/FeatureSetJsonByteConverterTest.java b/core/src/test/java/feast/core/job/option/FeatureSetJsonByteConverterTest.java
new file mode 100644
index 00000000000..a12452b593b
--- /dev/null
+++ b/core/src/test/java/feast/core/job/option/FeatureSetJsonByteConverterTest.java
@@ -0,0 +1,81 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright 2018-2020 The Feast Authors
+ *
+ * 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 feast.core.job.option;
+
+import static org.junit.Assert.*;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+import feast.proto.core.FeatureSetProto;
+import feast.proto.core.SourceProto;
+import feast.proto.types.ValueProto;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.junit.Test;
+
+public class FeatureSetJsonByteConverterTest {
+
+  private FeatureSetProto.FeatureSet newFeatureSet(Integer numberOfFeatures) {
+    List features =
+        IntStream.range(1, numberOfFeatures + 1)
+            .mapToObj(
+                i ->
+                    FeatureSetProto.FeatureSpec.newBuilder()
+                        .setValueType(ValueProto.ValueType.Enum.FLOAT)
+                        .setName("feature".concat(Integer.toString(i)))
+                        .build())
+            .collect(Collectors.toList());
+
+    return FeatureSetProto.FeatureSet.newBuilder()
+        .setSpec(
+            FeatureSetProto.FeatureSetSpec.newBuilder()
+                .setSource(
+                    SourceProto.Source.newBuilder()
+                        .setType(SourceProto.SourceType.KAFKA)
+                        .setKafkaSourceConfig(
+                            SourceProto.KafkaSourceConfig.newBuilder()
+                                .setBootstrapServers("somebrokers:9092")
+                                .setTopic("sometopic")))
+                .addAllFeatures(features)
+                .addEntities(
+                    FeatureSetProto.EntitySpec.newBuilder()
+                        .setName("entity")
+                        .setValueType(ValueProto.ValueType.Enum.STRING)))
+        .build();
+  }
+
+  @Test
+  public void shouldConvertFeatureSetsAsJsonStringBytes() throws InvalidProtocolBufferException {
+    int nrOfFeatureSet = 1;
+    int nrOfFeatures = 1;
+    List featureSets =
+        IntStream.range(1, nrOfFeatureSet + 1)
+            .mapToObj(i -> newFeatureSet(nrOfFeatures))
+            .collect(Collectors.toList());
+
+    String expectedOutputString =
+        "{\"entities\":[{\"name\":\"entity\",\"valueType\":2}],"
+            + "\"features\":[{\"name\":\"feature1\",\"valueType\":6}],"
+            + "\"source\":{"
+            + "\"type\":1,"
+            + "\"kafkaSourceConfig\":{"
+            + "\"bootstrapServers\":\"somebrokers:9092\","
+            + "\"topic\":\"sometopic\"}}}";
+    FeatureSetJsonByteConverter byteConverter = new FeatureSetJsonByteConverter();
+    assertEquals(expectedOutputString, new String(byteConverter.toByte(featureSets)));
+  }
+}
diff --git a/core/src/test/java/feast/core/model/FeatureSetTest.java b/core/src/test/java/feast/core/model/FeatureSetTest.java
new file mode 100644
index 00000000000..270dc3f3bcd
--- /dev/null
+++ b/core/src/test/java/feast/core/model/FeatureSetTest.java
@@ -0,0 +1,205 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright 2018-2020 The Feast Authors
+ *
+ * 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 feast.core.model;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+import com.google.protobuf.Duration;
+import com.google.protobuf.InvalidProtocolBufferException;
+import feast.proto.core.FeatureSetProto;
+import feast.proto.core.FeatureSetProto.EntitySpec;
+import feast.proto.core.FeatureSetProto.FeatureSetSpec;
+import feast.proto.core.FeatureSetProto.FeatureSetStatus;
+import feast.proto.core.FeatureSetProto.FeatureSpec;
+import feast.proto.core.SourceProto;
+import feast.proto.core.SourceProto.KafkaSourceConfig;
+import feast.proto.core.SourceProto.SourceType;
+import feast.proto.types.ValueProto.ValueType.Enum;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.tensorflow.metadata.v0.IntDomain;
+
+public class FeatureSetTest {
+  @Rule public final ExpectedException expectedException = ExpectedException.none();
+
+  private FeatureSetProto.FeatureSet oldFeatureSetProto;
+
+  @Before
+  public void setUp() {
+    SourceProto.Source oldSource =
+        SourceProto.Source.newBuilder()
+            .setType(SourceType.KAFKA)
+            .setKafkaSourceConfig(
+                KafkaSourceConfig.newBuilder()
+                    .setBootstrapServers("kafka:9092")
+                    .setTopic("mytopic"))
+            .build();
+
+    oldFeatureSetProto =
+        FeatureSetProto.FeatureSet.newBuilder()
+            .setSpec(
+                FeatureSetSpec.newBuilder()
+                    .setName("featureSet")
+                    .setProject("project")
+                    .setMaxAge(Duration.newBuilder().setSeconds(100))
+                    .setSource(oldSource)
+                    .addFeatures(
+                        FeatureSpec.newBuilder().setName("feature1").setValueType(Enum.INT64))
+                    .addFeatures(
+                        FeatureSpec.newBuilder().setName("feature2").setValueType(Enum.STRING))
+                    .addEntities(
+                        EntitySpec.newBuilder().setName("entity").setValueType(Enum.STRING))
+                    .build())
+            .build();
+  }
+
+  @Test
+  public void shouldUpdateFromProto() throws InvalidProtocolBufferException {
+    SourceProto.Source newSource =
+        SourceProto.Source.newBuilder()
+            .setType(SourceType.KAFKA)
+            .setKafkaSourceConfig(
+                KafkaSourceConfig.newBuilder()
+                    .setBootstrapServers("kafka:9092")
+                    .setTopic("mytopic-changed"))
+            .build();
+
+    FeatureSetProto.FeatureSet newFeatureSetProto =
+        FeatureSetProto.FeatureSet.newBuilder()
+            .setSpec(
+                FeatureSetSpec.newBuilder()
+                    .setName("featureSet")
+                    .setProject("project")
+                    .setMaxAge(Duration.newBuilder().setSeconds(101))
+                    .setSource(newSource)
+                    .addFeatures(
+                        FeatureSpec.newBuilder()
+                            .setName("feature1")
+                            .setValueType(Enum.INT64)
+                            .setIntDomain(IntDomain.newBuilder().setMax(10).setMin(0)))
+                    .addFeatures(
+                        FeatureSpec.newBuilder().setName("feature3").setValueType(Enum.STRING))
+                    .addEntities(
+                        EntitySpec.newBuilder().setName("entity").setValueType(Enum.STRING))
+                    .build())
+            .build();
+
+    FeatureSet actual = FeatureSet.fromProto(oldFeatureSetProto);
+    actual.updateFromProto(newFeatureSetProto);
+
+    FeatureSet expected = FeatureSet.fromProto(newFeatureSetProto);
+    Feature archivedFeature =
+        Feature.fromProto(
+            FeatureSpec.newBuilder().setName("feature2").setValueType(Enum.STRING).build());
+    archivedFeature.setArchived(true);
+    expected.addFeature(archivedFeature);
+    assertThat(actual, equalTo(expected));
+  }
+
+  @Test
+  public void shouldNotUpdateIfNoChange() throws InvalidProtocolBufferException {
+    FeatureSet actual = FeatureSet.fromProto(oldFeatureSetProto);
+    actual.setStatus(FeatureSetStatus.STATUS_READY);
+    actual.updateFromProto(oldFeatureSetProto);
+
+    FeatureSet expected = FeatureSet.fromProto(oldFeatureSetProto);
+    expected.setStatus(FeatureSetStatus.STATUS_READY);
+
+    assertThat(actual, equalTo(expected));
+  }
+
+  @Test
+  public void shouldThrowExceptionIfUpdateWithEntitiesChanged()
+      throws InvalidProtocolBufferException {
+    SourceProto.Source newSource =
+        SourceProto.Source.newBuilder()
+            .setType(SourceType.KAFKA)
+            .setKafkaSourceConfig(
+                KafkaSourceConfig.newBuilder()
+                    .setBootstrapServers("kafka:9092")
+                    .setTopic("mytopic-changed"))
+            .build();
+
+    FeatureSetProto.FeatureSet newFeatureSetProto =
+        FeatureSetProto.FeatureSet.newBuilder()
+            .setSpec(
+                FeatureSetSpec.newBuilder()
+                    .setName("featureSet")
+                    .setProject("project")
+                    .setMaxAge(Duration.newBuilder().setSeconds(101))
+                    .setSource(newSource)
+                    .addFeatures(
+                        FeatureSpec.newBuilder()
+                            .setName("feature1")
+                            .setValueType(Enum.INT64)
+                            .setIntDomain(IntDomain.newBuilder().setMax(10).setMin(0)))
+                    .addFeatures(
+                        FeatureSpec.newBuilder().setName("feature3").setValueType(Enum.STRING))
+                    .addEntities(EntitySpec.newBuilder().setName("entity").setValueType(Enum.FLOAT))
+                    .build())
+            .build();
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage(containsString("does not match existing set of entities"));
+    FeatureSet existingFeatureSet = FeatureSet.fromProto(oldFeatureSetProto);
+    existingFeatureSet.updateFromProto(newFeatureSetProto);
+  }
+
+  @Test
+  public void shouldThrowExceptionIfUpdateWithFeatureTypesChanged()
+      throws InvalidProtocolBufferException {
+    SourceProto.Source newSource =
+        SourceProto.Source.newBuilder()
+            .setType(SourceType.KAFKA)
+            .setKafkaSourceConfig(
+                KafkaSourceConfig.newBuilder()
+                    .setBootstrapServers("kafka:9092")
+                    .setTopic("mytopic-changed"))
+            .build();
+
+    FeatureSetProto.FeatureSet newFeatureSetProto =
+        FeatureSetProto.FeatureSet.newBuilder()
+            .setSpec(
+                FeatureSetSpec.newBuilder()
+                    .setName("featureSet")
+                    .setProject("project")
+                    .setMaxAge(Duration.newBuilder().setSeconds(101))
+                    .setSource(newSource)
+                    .addFeatures(
+                        FeatureSpec.newBuilder()
+                            .setName("feature1")
+                            .setValueType(Enum.INT64)
+                            .setIntDomain(IntDomain.newBuilder().setMax(10).setMin(0)))
+                    .addFeatures(
+                        FeatureSpec.newBuilder().setName("feature2").setValueType(Enum.FLOAT))
+                    .addEntities(
+                        EntitySpec.newBuilder().setName("entity").setValueType(Enum.STRING))
+                    .build())
+            .build();
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage(
+        containsString(
+            "You are attempting to change the type of feature feature2 from STRING to FLOAT."));
+    FeatureSet existingFeatureSet = FeatureSet.fromProto(oldFeatureSetProto);
+    existingFeatureSet.updateFromProto(newFeatureSetProto);
+  }
+}
diff --git a/core/src/test/java/feast/core/model/JobStatusTest.java b/core/src/test/java/feast/core/model/JobStatusTest.java
new file mode 100644
index 00000000000..f5c8839386c
--- /dev/null
+++ b/core/src/test/java/feast/core/model/JobStatusTest.java
@@ -0,0 +1,45 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright 2018-2020 The Feast Authors
+ *
+ * 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 feast.core.model;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Test;
+
+public class JobStatusTest {
+
+  @Test
+  public void isTerminalReturnsTrueForJobStatusWithTerminalState() {
+    JobStatus.getTerminalStates()
+        .forEach(
+            status -> {
+              assertThat(status.isTerminal(), is(true));
+              assertThat(status.isTransitional(), is(false));
+            });
+  }
+
+  @Test
+  public void isTransitionalReturnsTrueForJobStatusWithTransitionalState() {
+    JobStatus.getTransitionalStates()
+        .forEach(
+            status -> {
+              assertThat(status.isTransitional(), is(true));
+              assertThat(status.isTerminal(), is(false));
+            });
+  }
+}
diff --git a/core/src/test/java/feast/core/service/AccessManagementServiceTest.java b/core/src/test/java/feast/core/service/AccessManagementServiceTest.java
new file mode 100644
index 00000000000..15be203709f
--- /dev/null
+++ b/core/src/test/java/feast/core/service/AccessManagementServiceTest.java
@@ -0,0 +1,74 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright 2018-2019 The Feast Authors
+ *
+ * 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 feast.core.service;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import feast.core.dao.ProjectRepository;
+import feast.core.model.Project;
+import java.util.Optional;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.Mock;
+
+public class AccessManagementServiceTest {
+  @Rule public ExpectedException expectedException = ExpectedException.none();
+  // mocks
+  @Mock private ProjectRepository projectRepository;
+  // dummy models
+  private Project defaultProject;
+  private Project testProject;
+
+  // test target
+  private AccessManagementService accessService;
+
+  @Before
+  public void setup() {
+    initMocks(this);
+    // setup dummy models for testing
+    this.defaultProject = new Project(Project.DEFAULT_NAME);
+    this.testProject = new Project("project");
+    // setup test target
+    when(this.projectRepository.existsById(Project.DEFAULT_NAME)).thenReturn(false);
+    this.accessService = new AccessManagementService(this.projectRepository);
+  }
+
+  @Test
+  public void testDefaultProjectCreateInConstructor() {
+    verify(this.projectRepository).saveAndFlush(this.defaultProject);
+  }
+
+  @Test
+  public void testArchiveProject() {
+    when(this.projectRepository.findById("project")).thenReturn(Optional.of(this.testProject));
+    this.accessService.archiveProject("project");
+    this.testProject.setArchived(true);
+    verify(this.projectRepository).saveAndFlush(this.testProject);
+    // reset archived flag
+    this.testProject.setArchived(false);
+  }
+
+  @Test
+  public void shouldNotArchiveDefaultProject() {
+    expectedException.expect(IllegalArgumentException.class);
+    this.accessService.archiveProject(Project.DEFAULT_NAME);
+  }
+}
diff --git a/core/src/test/java/feast/core/service/JobCoordinatorServiceTest.java b/core/src/test/java/feast/core/service/JobCoordinatorServiceTest.java
index 775cb028b02..8386efb28f4 100644
--- a/core/src/test/java/feast/core/service/JobCoordinatorServiceTest.java
+++ b/core/src/test/java/feast/core/service/JobCoordinatorServiceTest.java
@@ -25,21 +25,10 @@
 import static org.mockito.Mockito.when;
 import static org.mockito.MockitoAnnotations.initMocks;
 
+import com.google.common.collect.Lists;
 import com.google.protobuf.InvalidProtocolBufferException;
-import feast.core.CoreServiceProto.ListFeatureSetsRequest.Filter;
-import feast.core.CoreServiceProto.ListFeatureSetsResponse;
-import feast.core.CoreServiceProto.ListStoresResponse;
-import feast.core.FeatureSetProto;
-import feast.core.FeatureSetProto.FeatureSetMeta;
-import feast.core.FeatureSetProto.FeatureSetSpec;
-import feast.core.SourceProto.KafkaSourceConfig;
-import feast.core.SourceProto.Source;
-import feast.core.SourceProto.SourceType;
-import feast.core.StoreProto;
-import feast.core.StoreProto.Store.RedisConfig;
-import feast.core.StoreProto.Store.StoreType;
-import feast.core.StoreProto.Store.Subscription;
-import feast.core.config.FeastProperties.JobUpdatesProperties;
+import feast.core.config.FeastProperties;
+import feast.core.config.FeastProperties.JobProperties;
 import feast.core.dao.FeatureSetRepository;
 import feast.core.dao.JobRepository;
 import feast.core.job.JobManager;
@@ -48,7 +37,21 @@
 import feast.core.model.FeatureSet;
 import feast.core.model.Job;
 import feast.core.model.JobStatus;
+import feast.proto.core.CoreServiceProto.ListFeatureSetsRequest.Filter;
+import feast.proto.core.CoreServiceProto.ListFeatureSetsResponse;
+import feast.proto.core.CoreServiceProto.ListStoresResponse;
+import feast.proto.core.FeatureSetProto;
+import feast.proto.core.FeatureSetProto.FeatureSetMeta;
+import feast.proto.core.FeatureSetProto.FeatureSetSpec;
+import feast.proto.core.SourceProto.KafkaSourceConfig;
+import feast.proto.core.SourceProto.Source;
+import feast.proto.core.SourceProto.SourceType;
+import feast.proto.core.StoreProto;
+import feast.proto.core.StoreProto.Store.RedisConfig;
+import feast.proto.core.StoreProto.Store.StoreType;
+import feast.proto.core.StoreProto.Store.Subscription;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import org.junit.Before;
 import org.junit.Rule;
@@ -65,21 +68,23 @@ public class JobCoordinatorServiceTest {
   @Mock SpecService specService;
   @Mock FeatureSetRepository featureSetRepository;
 
-  private JobUpdatesProperties jobUpdatesProperties;
+  private FeastProperties feastProperties;
 
   @Before
   public void setUp() {
     initMocks(this);
-    jobUpdatesProperties = new JobUpdatesProperties();
-    jobUpdatesProperties.setTimeoutSeconds(5);
+    feastProperties = new FeastProperties();
+    JobProperties jobProperties = new JobProperties();
+    jobProperties.setJobUpdateTimeoutSeconds(5);
+    feastProperties.setJobs(jobProperties);
   }
 
   @Test
-  public void shouldDoNothingIfNoStoresFound() {
+  public void shouldDoNothingIfNoStoresFound() throws InvalidProtocolBufferException {
     when(specService.listStores(any())).thenReturn(ListStoresResponse.newBuilder().build());
     JobCoordinatorService jcs =
         new JobCoordinatorService(
-            jobRepository, featureSetRepository, specService, jobManager, jobUpdatesProperties);
+            jobRepository, featureSetRepository, specService, jobManager, feastProperties);
     jcs.Poll();
     verify(jobRepository, times(0)).saveAndFlush(any());
   }
@@ -91,21 +96,16 @@ public void shouldDoNothingIfNoMatchingFeatureSetsFound() throws InvalidProtocol
             .setName("test")
             .setType(StoreType.REDIS)
             .setRedisConfig(RedisConfig.newBuilder().build())
-            .addSubscriptions(
-                Subscription.newBuilder().setProject("*").setName("*").setVersion("*").build())
+            .addSubscriptions(Subscription.newBuilder().setProject("*").setName("*").build())
             .build();
     when(specService.listStores(any()))
         .thenReturn(ListStoresResponse.newBuilder().addStore(store).build());
     when(specService.listFeatureSets(
-            Filter.newBuilder()
-                .setProject("*")
-                .setFeatureSetName("*")
-                .setFeatureSetVersion("*")
-                .build()))
+            Filter.newBuilder().setProject("*").setFeatureSetName("*").build()))
         .thenReturn(ListFeatureSetsResponse.newBuilder().build());
     JobCoordinatorService jcs =
         new JobCoordinatorService(
-            jobRepository, featureSetRepository, specService, jobManager, jobUpdatesProperties);
+            jobRepository, featureSetRepository, specService, jobManager, feastProperties);
     jcs.Poll();
     verify(jobRepository, times(0)).saveAndFlush(any());
   }
@@ -117,12 +117,7 @@ public void shouldGenerateAndSubmitJobsIfAny() throws InvalidProtocolBufferExcep
             .setName("test")
             .setType(StoreType.REDIS)
             .setRedisConfig(RedisConfig.newBuilder().build())
-            .addSubscriptions(
-                Subscription.newBuilder()
-                    .setProject("project1")
-                    .setName("features")
-                    .setVersion("*")
-                    .build())
+            .addSubscriptions(Subscription.newBuilder().setProject("project1").setName("*").build())
             .build();
     Source source =
         Source.newBuilder()
@@ -134,60 +129,51 @@ public void shouldGenerateAndSubmitJobsIfAny() throws InvalidProtocolBufferExcep
                     .build())
             .build();
 
-    FeatureSetProto.FeatureSet featureSet1 =
+    FeatureSetProto.FeatureSet featureSetProto1 =
         FeatureSetProto.FeatureSet.newBuilder()
             .setSpec(
                 FeatureSetSpec.newBuilder()
                     .setSource(source)
                     .setProject("project1")
-                    .setName("features")
-                    .setVersion(1))
+                    .setName("features1"))
             .setMeta(FeatureSetMeta.newBuilder())
             .build();
-    FeatureSetProto.FeatureSet featureSet2 =
+    FeatureSet featureSet1 = FeatureSet.fromProto(featureSetProto1);
+    FeatureSetProto.FeatureSet featureSetProto2 =
         FeatureSetProto.FeatureSet.newBuilder()
             .setSpec(
                 FeatureSetSpec.newBuilder()
                     .setSource(source)
                     .setProject("project1")
-                    .setName("features")
-                    .setVersion(2))
+                    .setName("features2"))
             .setMeta(FeatureSetMeta.newBuilder())
             .build();
+    FeatureSet featureSet2 = FeatureSet.fromProto(featureSetProto2);
     String extId = "ext";
-    ArgumentCaptor jobArgCaptor = ArgumentCaptor.forClass(Job.class);
+    ArgumentCaptor> jobArgCaptor = ArgumentCaptor.forClass(List.class);
 
     Job expectedInput =
         new Job(
             "",
             "",
-            Runner.DATAFLOW.getName(),
+            Runner.DATAFLOW,
             feast.core.model.Source.fromProto(source),
             feast.core.model.Store.fromProto(store),
-            Arrays.asList(FeatureSet.fromProto(featureSet1), FeatureSet.fromProto(featureSet2)),
+            Arrays.asList(featureSet1, featureSet2),
             JobStatus.PENDING);
 
     Job expected =
         new Job(
             "some_id",
             extId,
-            Runner.DATAFLOW.getName(),
+            Runner.DATAFLOW,
             feast.core.model.Source.fromProto(source),
             feast.core.model.Store.fromProto(store),
-            Arrays.asList(FeatureSet.fromProto(featureSet1), FeatureSet.fromProto(featureSet2)),
+            Arrays.asList(featureSet1, featureSet2),
             JobStatus.RUNNING);
 
-    when(specService.listFeatureSets(
-            Filter.newBuilder()
-                .setProject("project1")
-                .setFeatureSetName("features")
-                .setFeatureSetVersion("*")
-                .build()))
-        .thenReturn(
-            ListFeatureSetsResponse.newBuilder()
-                .addFeatureSets(featureSet1)
-                .addFeatureSets(featureSet2)
-                .build());
+    when(featureSetRepository.findAllByNameLikeAndProject_NameLikeOrderByNameAsc("%", "project1"))
+        .thenReturn(Lists.newArrayList(featureSet1, featureSet2));
     when(specService.listStores(any()))
         .thenReturn(ListStoresResponse.newBuilder().addStore(store).build());
 
@@ -196,11 +182,11 @@ public void shouldGenerateAndSubmitJobsIfAny() throws InvalidProtocolBufferExcep
 
     JobCoordinatorService jcs =
         new JobCoordinatorService(
-            jobRepository, featureSetRepository, specService, jobManager, jobUpdatesProperties);
+            jobRepository, featureSetRepository, specService, jobManager, feastProperties);
     jcs.Poll();
-    verify(jobRepository, times(1)).saveAndFlush(jobArgCaptor.capture());
-    Job actual = jobArgCaptor.getValue();
-    assertThat(actual, equalTo(expected));
+    verify(jobRepository, times(1)).saveAll(jobArgCaptor.capture());
+    List actual = jobArgCaptor.getValue();
+    assertThat(actual, equalTo(Collections.singletonList(expected)));
   }
 
   @Test
@@ -210,12 +196,7 @@ public void shouldGroupJobsBySource() throws InvalidProtocolBufferException {
             .setName("test")
             .setType(StoreType.REDIS)
             .setRedisConfig(RedisConfig.newBuilder().build())
-            .addSubscriptions(
-                Subscription.newBuilder()
-                    .setProject("project1")
-                    .setName("features")
-                    .setVersion("*")
-                    .build())
+            .addSubscriptions(Subscription.newBuilder().setProject("project1").setName("*").build())
             .build();
     Source source1 =
         Source.newBuilder()
@@ -236,79 +217,72 @@ public void shouldGroupJobsBySource() throws InvalidProtocolBufferException {
                     .build())
             .build();
 
-    FeatureSetProto.FeatureSet featureSet1 =
+    FeatureSetProto.FeatureSet featureSetProto1 =
         FeatureSetProto.FeatureSet.newBuilder()
             .setSpec(
                 FeatureSetSpec.newBuilder()
                     .setSource(source1)
                     .setProject("project1")
-                    .setName("features")
-                    .setVersion(1))
+                    .setName("features1"))
             .setMeta(FeatureSetMeta.newBuilder())
             .build();
-    FeatureSetProto.FeatureSet featureSet2 =
+    FeatureSet featureSet1 = FeatureSet.fromProto(featureSetProto1);
+
+    FeatureSetProto.FeatureSet featureSetProto2 =
         FeatureSetProto.FeatureSet.newBuilder()
             .setSpec(
                 FeatureSetSpec.newBuilder()
                     .setSource(source2)
                     .setProject("project1")
-                    .setName("features")
-                    .setVersion(2))
+                    .setName("features2"))
             .setMeta(FeatureSetMeta.newBuilder())
             .build();
+    FeatureSet featureSet2 = FeatureSet.fromProto(featureSetProto2);
 
     Job expectedInput1 =
         new Job(
             "name1",
             "",
-            Runner.DATAFLOW.getName(),
+            Runner.DATAFLOW,
             feast.core.model.Source.fromProto(source1),
             feast.core.model.Store.fromProto(store),
-            Arrays.asList(FeatureSet.fromProto(featureSet1)),
+            Arrays.asList(featureSet1),
             JobStatus.PENDING);
 
     Job expected1 =
         new Job(
             "name1",
             "extId1",
-            Runner.DATAFLOW.getName(),
+            Runner.DATAFLOW,
             feast.core.model.Source.fromProto(source1),
             feast.core.model.Store.fromProto(store),
-            Arrays.asList(FeatureSet.fromProto(featureSet1)),
+            Arrays.asList(featureSet1),
             JobStatus.RUNNING);
 
     Job expectedInput2 =
         new Job(
             "",
             "extId2",
-            Runner.DATAFLOW.getName(),
+            Runner.DATAFLOW,
             feast.core.model.Source.fromProto(source2),
             feast.core.model.Store.fromProto(store),
-            Arrays.asList(FeatureSet.fromProto(featureSet2)),
+            Arrays.asList(featureSet2),
             JobStatus.PENDING);
 
     Job expected2 =
         new Job(
             "name2",
             "extId2",
-            Runner.DATAFLOW.getName(),
+            Runner.DATAFLOW,
             feast.core.model.Source.fromProto(source2),
             feast.core.model.Store.fromProto(store),
-            Arrays.asList(FeatureSet.fromProto(featureSet2)),
+            Arrays.asList(featureSet2),
             JobStatus.RUNNING);
-    ArgumentCaptor jobArgCaptor = ArgumentCaptor.forClass(Job.class);
+    ArgumentCaptor> jobArgCaptor = ArgumentCaptor.forClass(List.class);
+
+    when(featureSetRepository.findAllByNameLikeAndProject_NameLikeOrderByNameAsc("%", "project1"))
+        .thenReturn(Lists.newArrayList(featureSet1, featureSet2));
 
-    when(specService.listFeatureSets(
-            Filter.newBuilder()
-                .setProject("project1")
-                .setFeatureSetName("features")
-                .setFeatureSetVersion("*")
-                .build()))
-        .thenReturn(
-            ListFeatureSetsResponse.newBuilder()
-                .addFeatureSets(featureSet1)
-                .addFeatureSets(featureSet2)
-                .build());
     when(specService.listStores(any()))
         .thenReturn(ListStoresResponse.newBuilder().addStore(store).build());
 
@@ -318,11 +292,11 @@ public void shouldGroupJobsBySource() throws InvalidProtocolBufferException {
 
     JobCoordinatorService jcs =
         new JobCoordinatorService(
-            jobRepository, featureSetRepository, specService, jobManager, jobUpdatesProperties);
+            jobRepository, featureSetRepository, specService, jobManager, feastProperties);
     jcs.Poll();
 
-    verify(jobRepository, times(2)).saveAndFlush(jobArgCaptor.capture());
-    List actual = jobArgCaptor.getAllValues();
+    verify(jobRepository, times(1)).saveAll(jobArgCaptor.capture());
+    List actual = jobArgCaptor.getValue();
 
     assertThat(actual.get(0), equalTo(expected1));
     assertThat(actual.get(1), equalTo(expected2));
diff --git a/core/src/test/java/feast/core/service/JobServiceTest.java b/core/src/test/java/feast/core/service/JobServiceTest.java
new file mode 100644
index 00000000000..ff056287f9b
--- /dev/null
+++ b/core/src/test/java/feast/core/service/JobServiceTest.java
@@ -0,0 +1,406 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright 2018-2020 The Feast Authors
+ *
+ * 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 feast.core.service;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+import feast.core.dao.JobRepository;
+import feast.core.job.JobManager;
+import feast.core.job.Runner;
+import feast.core.model.*;
+import feast.proto.core.CoreServiceProto.ListFeatureSetsRequest;
+import feast.proto.core.CoreServiceProto.ListFeatureSetsResponse;
+import feast.proto.core.CoreServiceProto.ListIngestionJobsRequest;
+import feast.proto.core.CoreServiceProto.ListIngestionJobsResponse;
+import feast.proto.core.CoreServiceProto.RestartIngestionJobRequest;
+import feast.proto.core.CoreServiceProto.RestartIngestionJobResponse;
+import feast.proto.core.CoreServiceProto.StopIngestionJobRequest;
+import feast.proto.core.CoreServiceProto.StopIngestionJobResponse;
+import feast.proto.core.FeatureSetReferenceProto.FeatureSetReference;
+import feast.proto.core.IngestionJobProto.IngestionJob;
+import feast.proto.core.StoreProto.Store.RedisConfig;
+import feast.proto.core.StoreProto.Store.StoreType;
+import feast.proto.types.ValueProto.ValueType.Enum;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+public class JobServiceTest {
+  // mocks
+  @Mock private JobRepository jobRepository;
+  @Mock private JobManager jobManager;
+  @Mock private SpecService specService;
+  // fake models
+  private Source dataSource;
+  private Store dataStore;
+  private FeatureSet featureSet;
+  private List fsReferences;
+  private List listFilters;
+  private Job job;
+  private IngestionJob ingestionJob;
+  // test target
+  public JobService jobService;
+
+  @Before
+  public void setup() {
+    initMocks(this);
+
+    // create mock objects for testing
+    // fake data source
+    this.dataSource = TestObjectFactory.defaultSource;
+    // fake data store
+    this.dataStore =
+        new Store(
+            "feast-redis",
+            StoreType.REDIS.toString(),
+            RedisConfig.newBuilder().setPort(6379).build().toByteArray(),
+            "*:*:*");
+
+    // fake featureset & job
+    this.featureSet = this.newDummyFeatureSet("food", 2, "hunger");
+    this.job = this.newDummyJob("kafka-to-redis", "job-1111", JobStatus.PENDING);
+    try {
+      this.ingestionJob = this.job.toProto();
+    } catch (InvalidProtocolBufferException e) {
+      e.printStackTrace();
+    }
+
+    this.fsReferences = this.newDummyFeatureSetReferences();
+    this.listFilters = this.newDummyListRequestFilters();
+
+    // setup mock objects
+    this.setupSpecService();
+    this.setupJobRepository();
+    this.setupJobManager();
+
+    // create test target
+    this.jobService =
+        new JobService(this.jobRepository, this.specService, Arrays.asList(this.jobManager));
+  }
+
+  public void setupSpecService() {
+    try {
+      ListFeatureSetsResponse response =
+          ListFeatureSetsResponse.newBuilder().addFeatureSets(this.featureSet.toProto()).build();
+
+      when(this.specService.listFeatureSets(this.listFilters.get(0))).thenReturn(response);
+
+      when(this.specService.listFeatureSets(this.listFilters.get(1))).thenReturn(response);
+
+      when(this.specService.listFeatureSets(this.listFilters.get(2))).thenReturn(response);
+    } catch (InvalidProtocolBufferException e) {
+      e.printStackTrace();
+      fail("Unexpected exception");
+    }
+  }
+
+  public void setupJobRepository() {
+    when(this.jobRepository.findById(this.job.getId())).thenReturn(Optional.of(this.job));
+    when(this.jobRepository.findByStoreName(this.dataStore.getName()))
+        .thenReturn(Arrays.asList(this.job));
+    when(this.jobRepository.findByFeatureSetsIn(Arrays.asList(this.featureSet)))
+        .thenReturn(Arrays.asList(this.job));
+    when(this.jobRepository.findAll()).thenReturn(Arrays.asList(this.job));
+  }
+
+  public void setupJobManager() {
+    when(this.jobManager.getRunnerType()).thenReturn(Runner.DATAFLOW);
+    when(this.jobManager.restartJob(this.job))
+        .thenReturn(this.newDummyJob(this.job.getId(), this.job.getExtId(), JobStatus.PENDING));
+  }
+
+  private FeatureSet newDummyFeatureSet(String name, int version, String project) {
+    Feature feature = TestObjectFactory.CreateFeature(name + "_feature", Enum.INT64);
+    Entity entity = TestObjectFactory.CreateEntity(name + "_entity", Enum.STRING);
+
+    FeatureSet fs =
+        TestObjectFactory.CreateFeatureSet(
+            name, project, Arrays.asList(entity), Arrays.asList(feature));
+    fs.setCreated(Date.from(Instant.ofEpochSecond(10L)));
+    return fs;
+  }
+
+  private Job newDummyJob(String id, String extId, JobStatus status) {
+    return new Job(
+        id,
+        extId,
+        Runner.DATAFLOW,
+        this.dataSource,
+        this.dataStore,
+        Arrays.asList(this.featureSet),
+        status);
+  }
+
+  private List newDummyFeatureSetReferences() {
+    return Arrays.asList(
+        // all provided: name, version and project
+        FeatureSetReference.newBuilder()
+            .setName(this.featureSet.getName())
+            .setProject(this.featureSet.getProject().toString())
+            .build(),
+
+        // name and project
+        FeatureSetReference.newBuilder()
+            .setName(this.featureSet.getName())
+            .setProject(this.featureSet.getProject().toString())
+            .build(),
+
+        // name and version
+        FeatureSetReference.newBuilder().setName(this.featureSet.getName()).build());
+  }
+
+  private List newDummyListRequestFilters() {
+    return Arrays.asList(
+        // all provided: name, version and project
+        ListFeatureSetsRequest.Filter.newBuilder()
+            .setFeatureSetName(this.featureSet.getName())
+            .setProject(this.featureSet.getProject().toString())
+            .build(),
+
+        // name and project
+        ListFeatureSetsRequest.Filter.newBuilder()
+            .setFeatureSetName(this.featureSet.getName())
+            .setProject(this.featureSet.getProject().toString())
+            .build(),
+
+        // name and project
+        ListFeatureSetsRequest.Filter.newBuilder()
+            .setFeatureSetName(this.featureSet.getName())
+            .setProject("*")
+            .build());
+  }
+
+  private ListIngestionJobsResponse tryListJobs(ListIngestionJobsRequest request) {
+    ListIngestionJobsResponse response = null;
+    try {
+      response = this.jobService.listJobs(request);
+    } catch (InvalidProtocolBufferException e) {
+      e.printStackTrace();
+      fail("Caught Unexpected exception");
+    }
+
+    return response;
+  }
+
+  @Test
+  public void testListJobsById() {
+    ListIngestionJobsRequest.Filter filter =
+        ListIngestionJobsRequest.Filter.newBuilder().setId(this.job.getId()).build();
+    ListIngestionJobsRequest request =
+        ListIngestionJobsRequest.newBuilder().setFilter(filter).build();
+    assertThat(this.tryListJobs(request).getJobs(0), equalTo(this.ingestionJob));
+
+    // list with no filter
+    request = ListIngestionJobsRequest.newBuilder().build();
+    assertThat(this.tryListJobs(request).getJobs(0), equalTo(this.ingestionJob));
+
+    // list with empty filter
+    filter = ListIngestionJobsRequest.Filter.newBuilder().build();
+    request = ListIngestionJobsRequest.newBuilder().setFilter(filter).build();
+    assertThat(this.tryListJobs(request).getJobs(0), equalTo(this.ingestionJob));
+  }
+
+  @Test
+  public void testListJobsByStoreName() {
+    ListIngestionJobsRequest.Filter filter =
+        ListIngestionJobsRequest.Filter.newBuilder().setStoreName(this.dataStore.getName()).build();
+    ListIngestionJobsRequest request =
+        ListIngestionJobsRequest.newBuilder().setFilter(filter).build();
+    assertThat(this.tryListJobs(request).getJobs(0), equalTo(this.ingestionJob));
+  }
+
+  @Test
+  public void testListIngestionJobByFeatureSetReference() {
+    // list job by feature set reference: name and version and project
+    ListIngestionJobsRequest.Filter filter =
+        ListIngestionJobsRequest.Filter.newBuilder()
+            .setFeatureSetReference(this.fsReferences.get(0))
+            .setId(this.job.getId())
+            .build();
+    ListIngestionJobsRequest request =
+        ListIngestionJobsRequest.newBuilder().setFilter(filter).build();
+    assertThat(this.tryListJobs(request).getJobs(0), equalTo(this.ingestionJob));
+
+    // list job by feature set reference: name and version
+    filter =
+        ListIngestionJobsRequest.Filter.newBuilder()
+            .setFeatureSetReference(this.fsReferences.get(1))
+            .setId(this.job.getId())
+            .build();
+    request = ListIngestionJobsRequest.newBuilder().setFilter(filter).build();
+    assertThat(this.tryListJobs(request).getJobs(0), equalTo(this.ingestionJob));
+
+    // list job by feature set reference: name and project
+    filter =
+        ListIngestionJobsRequest.Filter.newBuilder()
+            .setFeatureSetReference(this.fsReferences.get(2))
+            .setId(this.job.getId())
+            .build();
+    request = ListIngestionJobsRequest.newBuilder().setFilter(filter).build();
+    assertThat(this.tryListJobs(request).getJobs(0), equalTo(this.ingestionJob));
+  }
+
+  private StopIngestionJobResponse tryStopJob(
+      StopIngestionJobRequest request, boolean expectError) {
+    StopIngestionJobResponse response = null;
+    try {
+      response = this.jobService.stopJob(request);
+      // expected exception, but none was thrown
+      if (expectError) {
+        fail("Expected exception, but none was thrown");
+      }
+    } catch (Exception e) {
+      if (expectError != true) {
+        // unexpected exception
+        e.printStackTrace();
+        fail("Caught Unexpected exception trying to restart job");
+      }
+    }
+
+    return response;
+  }
+
+  @Test
+  public void testStopJobForId() {
+    JobStatus prevStatus = this.job.getStatus();
+    this.job.setStatus(JobStatus.RUNNING);
+
+    StopIngestionJobRequest request =
+        StopIngestionJobRequest.newBuilder().setId(this.job.getId()).build();
+    this.tryStopJob(request, false);
+    verify(this.jobManager).abortJob(this.job.getExtId());
+
+    // TODO: check that for job status change in featureset source
+
+    this.job.setStatus(prevStatus);
+  }
+
+  @Test
+  public void testStopAlreadyStopped() {
+    // check that stop jobs does not trying to stop jobs that are not already stopped
+    List doNothingStatuses = new ArrayList<>(JobStatus.getTerminalStates());
+
+    JobStatus prevStatus = this.job.getStatus();
+    for (JobStatus status : doNothingStatuses) {
+      this.job.setStatus(status);
+
+      StopIngestionJobRequest request =
+          StopIngestionJobRequest.newBuilder().setId(this.job.getId()).build();
+      this.tryStopJob(request, false);
+
+      verify(this.jobManager, never()).abortJob(this.job.getExtId());
+    }
+
+    this.job.setStatus(prevStatus);
+  }
+
+  @Test
+  public void testStopUnsupportedError() {
+    // check for UnsupportedOperationException when trying to stop jobs are
+    // in an in unknown or in a transitional state
+    JobStatus prevStatus = this.job.getStatus();
+    List unsupportedStatuses = new ArrayList<>();
+    unsupportedStatuses.addAll(JobStatus.getTransitionalStates());
+    unsupportedStatuses.add(JobStatus.UNKNOWN);
+
+    for (JobStatus status : unsupportedStatuses) {
+      this.job.setStatus(status);
+
+      StopIngestionJobRequest request =
+          StopIngestionJobRequest.newBuilder().setId(this.job.getId()).build();
+      this.tryStopJob(request, true);
+    }
+
+    this.job.setStatus(prevStatus);
+  }
+
+  // restart jobs
+  private RestartIngestionJobResponse tryRestartJob(
+      RestartIngestionJobRequest request, boolean expectError) {
+    RestartIngestionJobResponse response = null;
+    try {
+      response = this.jobService.restartJob(request);
+      // expected exception, but none was thrown
+      if (expectError) {
+        fail("Expected exception, but none was thrown");
+      }
+    } catch (Exception e) {
+      if (expectError != true) {
+        // unexpected exception
+        e.printStackTrace();
+        fail("Caught Unexpected exception trying to stop job");
+      }
+    }
+
+    return response;
+  }
+
+  @Test
+  public void testRestartJobForId() {
+    JobStatus prevStatus = this.job.getStatus();
+
+    // restart running job
+    this.job.setStatus(JobStatus.RUNNING);
+    RestartIngestionJobRequest request =
+        RestartIngestionJobRequest.newBuilder().setId(this.job.getId()).build();
+    this.tryRestartJob(request, false);
+
+    // restart terminated job
+    this.job.setStatus(JobStatus.SUSPENDED);
+    request = RestartIngestionJobRequest.newBuilder().setId(this.job.getId()).build();
+    this.tryRestartJob(request, false);
+
+    verify(this.jobManager, times(2)).restartJob(this.job);
+    verify(this.jobRepository, times(2)).saveAndFlush(this.job);
+
+    this.job.setStatus(prevStatus);
+  }
+
+  @Test
+  public void testRestartUnsupportedError() {
+    // check for UnsupportedOperationException when trying to restart jobs are
+    // in an in unknown or in a transitional state
+    JobStatus prevStatus = this.job.getStatus();
+    List unsupportedStatuses = new ArrayList<>();
+    unsupportedStatuses.addAll(JobStatus.getTransitionalStates());
+    unsupportedStatuses.add(JobStatus.UNKNOWN);
+
+    for (JobStatus status : unsupportedStatuses) {
+      this.job.setStatus(status);
+
+      RestartIngestionJobRequest request =
+          RestartIngestionJobRequest.newBuilder().setId(this.job.getId()).build();
+      this.tryRestartJob(request, true);
+    }
+
+    this.job.setStatus(prevStatus);
+  }
+}
diff --git a/core/src/test/java/feast/core/service/SpecServiceTest.java b/core/src/test/java/feast/core/service/SpecServiceTest.java
index edd99aa4940..e584ee71e00 100644
--- a/core/src/test/java/feast/core/service/SpecServiceTest.java
+++ b/core/src/test/java/feast/core/service/SpecServiceTest.java
@@ -1,6 +1,6 @@
 /*
  * SPDX-License-Identifier: Apache-2.0
- * Copyright 2018-2019 The Feast Authors
+ * Copyright 2018-2020 The Feast Authors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -26,43 +26,34 @@
 
 import com.google.api.client.util.Lists;
 import com.google.protobuf.InvalidProtocolBufferException;
-import feast.core.CoreServiceProto.ApplyFeatureSetResponse;
-import feast.core.CoreServiceProto.ApplyFeatureSetResponse.Status;
-import feast.core.CoreServiceProto.GetFeatureSetRequest;
-import feast.core.CoreServiceProto.GetFeatureSetResponse;
-import feast.core.CoreServiceProto.ListFeatureSetsRequest.Filter;
-import feast.core.CoreServiceProto.ListFeatureSetsResponse;
-import feast.core.CoreServiceProto.ListStoresRequest;
-import feast.core.CoreServiceProto.ListStoresResponse;
-import feast.core.CoreServiceProto.UpdateStoreRequest;
-import feast.core.CoreServiceProto.UpdateStoreResponse;
-import feast.core.FeatureSetProto;
-import feast.core.FeatureSetProto.FeatureSetSpec;
-import feast.core.FeatureSetProto.FeatureSetStatus;
-import feast.core.FeatureSetProto.FeatureSpec;
-import feast.core.SourceProto.KafkaSourceConfig;
-import feast.core.SourceProto.SourceType;
-import feast.core.StoreProto;
-import feast.core.StoreProto.Store.RedisConfig;
-import feast.core.StoreProto.Store.StoreType;
-import feast.core.StoreProto.Store.Subscription;
 import feast.core.dao.FeatureSetRepository;
 import feast.core.dao.ProjectRepository;
 import feast.core.dao.StoreRepository;
 import feast.core.exception.RetrievalException;
-import feast.core.model.FeatureSet;
-import feast.core.model.Field;
-import feast.core.model.Project;
-import feast.core.model.Source;
-import feast.core.model.Store;
-import feast.types.ValueProto.ValueType.Enum;
+import feast.core.model.*;
+import feast.proto.core.CoreServiceProto.ApplyFeatureSetResponse;
+import feast.proto.core.CoreServiceProto.ApplyFeatureSetResponse.Status;
+import feast.proto.core.CoreServiceProto.GetFeatureSetRequest;
+import feast.proto.core.CoreServiceProto.GetFeatureSetResponse;
+import feast.proto.core.CoreServiceProto.ListFeatureSetsRequest.Filter;
+import feast.proto.core.CoreServiceProto.ListFeatureSetsResponse;
+import feast.proto.core.CoreServiceProto.ListStoresRequest;
+import feast.proto.core.CoreServiceProto.ListStoresResponse;
+import feast.proto.core.CoreServiceProto.UpdateStoreRequest;
+import feast.proto.core.CoreServiceProto.UpdateStoreResponse;
+import feast.proto.core.FeatureSetProto;
+import feast.proto.core.FeatureSetProto.EntitySpec;
+import feast.proto.core.FeatureSetProto.FeatureSetSpec;
+import feast.proto.core.FeatureSetProto.FeatureSpec;
+import feast.proto.core.StoreProto;
+import feast.proto.core.StoreProto.Store.RedisConfig;
+import feast.proto.core.StoreProto.Store.StoreType;
+import feast.proto.core.StoreProto.Store.Subscription;
+import feast.proto.types.ValueProto.ValueType.Enum;
 import java.sql.Date;
 import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
+import java.util.*;
+import java.util.Map.Entry;
 import java.util.stream.Collectors;
 import org.junit.Before;
 import org.junit.Rule;
@@ -71,6 +62,21 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
+import org.tensorflow.metadata.v0.BoolDomain;
+import org.tensorflow.metadata.v0.FeaturePresence;
+import org.tensorflow.metadata.v0.FeaturePresenceWithinGroup;
+import org.tensorflow.metadata.v0.FixedShape;
+import org.tensorflow.metadata.v0.FloatDomain;
+import org.tensorflow.metadata.v0.ImageDomain;
+import org.tensorflow.metadata.v0.IntDomain;
+import org.tensorflow.metadata.v0.MIDDomain;
+import org.tensorflow.metadata.v0.NaturalLanguageDomain;
+import org.tensorflow.metadata.v0.StringDomain;
+import org.tensorflow.metadata.v0.StructDomain;
+import org.tensorflow.metadata.v0.TimeDomain;
+import org.tensorflow.metadata.v0.TimeOfDayDomain;
+import org.tensorflow.metadata.v0.URLDomain;
+import org.tensorflow.metadata.v0.ValueCount;
 
 public class SpecServiceTest {
 
@@ -87,69 +93,48 @@ public class SpecServiceTest {
   private List stores;
   private Source defaultSource;
 
+  // TODO: Updates update features in place, so if tests follow the wrong order they might break.
+  // Refactor this maybe?
   @Before
   public void setUp() {
     initMocks(this);
-    defaultSource =
-        new Source(
-            SourceType.KAFKA,
-            KafkaSourceConfig.newBuilder()
-                .setBootstrapServers("kafka:9092")
-                .setTopic("my-topic")
-                .build(),
-            true);
-
-    FeatureSet featureSet1v1 = newDummyFeatureSet("f1", 1, "project1");
-    FeatureSet featureSet1v2 = newDummyFeatureSet("f1", 2, "project1");
-    FeatureSet featureSet1v3 = newDummyFeatureSet("f1", 3, "project1");
-    FeatureSet featureSet2v1 = newDummyFeatureSet("f2", 1, "project1");
-
-    Field f3f1 = new Field("f3f1", Enum.INT64);
-    Field f3f2 = new Field("f3f2", Enum.INT64);
-    Field f3e1 = new Field("f3e1", Enum.STRING);
-    FeatureSet featureSet3v1 =
-        new FeatureSet(
-            "f3",
-            "project1",
-            1,
-            100L,
-            Arrays.asList(f3e1),
-            Arrays.asList(f3f2, f3f1),
-            defaultSource,
-            FeatureSetStatus.STATUS_READY);
-
-    featureSets =
-        Arrays.asList(featureSet1v1, featureSet1v2, featureSet1v3, featureSet2v1, featureSet3v1);
-    when(featureSetRepository.findAll()).thenReturn(featureSets);
-    when(featureSetRepository.findAllByOrderByNameAscVersionAsc()).thenReturn(featureSets);
+    defaultSource = TestObjectFactory.defaultSource;
+
+    FeatureSet featureSet1 = newDummyFeatureSet("f1", "project1");
+    FeatureSet featureSet2 = newDummyFeatureSet("f2", "project1");
+
+    Feature f3f1 = TestObjectFactory.CreateFeature("f3f1", Enum.INT64);
+    Feature f3f2 = TestObjectFactory.CreateFeature("f3f2", Enum.INT64);
+    Entity f3e1 = TestObjectFactory.CreateEntity("f3e1", Enum.STRING);
+    FeatureSet featureSet3 =
+        TestObjectFactory.CreateFeatureSet(
+            "f3", "project1", Arrays.asList(f3e1), Arrays.asList(f3f2, f3f1));
 
-    when(featureSetRepository.findFeatureSetByNameAndProject_NameAndVersion("f1", "project1", 1))
+    FeatureSet featureSet4 = newDummyFeatureSet("f4", Project.DEFAULT_NAME);
+    featureSets = Arrays.asList(featureSet1, featureSet2, featureSet3, featureSet4);
+
+    when(featureSetRepository.findAll()).thenReturn(featureSets);
+    when(featureSetRepository.findAllByOrderByNameAsc()).thenReturn(featureSets);
+    when(featureSetRepository.findFeatureSetByNameAndProject_Name("f1", "project1"))
         .thenReturn(featureSets.get(0));
-    when(featureSetRepository.findAllByNameLikeAndProject_NameOrderByNameAscVersionAsc(
-            "f1", "project1"))
-        .thenReturn(featureSets.subList(0, 3));
-    when(featureSetRepository.findAllByNameLikeAndProject_NameOrderByNameAscVersionAsc(
-            "f3", "project1"))
-        .thenReturn(featureSets.subList(4, 5));
-    when(featureSetRepository.findFirstFeatureSetByNameLikeAndProject_NameOrderByVersionDesc(
-            "f1", "project1"))
-        .thenReturn(featureSet1v3);
-    when(featureSetRepository.findAllByNameLikeAndProject_NameOrderByNameAscVersionAsc(
-            "f1", "project1"))
-        .thenReturn(featureSets.subList(0, 3));
-    when(featureSetRepository.findAllByNameLikeAndProject_NameOrderByNameAscVersionAsc(
-            "asd", "project1"))
+    when(featureSetRepository.findFeatureSetByNameAndProject_Name("f2", "project1"))
+        .thenReturn(featureSets.get(1));
+    when(featureSetRepository.findAllByNameLikeAndProject_NameOrderByNameAsc("f1", "project1"))
+        .thenReturn(featureSets.subList(0, 1));
+    when(featureSetRepository.findAllByNameLikeAndProject_NameOrderByNameAsc("asd", "project1"))
         .thenReturn(Lists.newArrayList());
-    when(featureSetRepository.findAllByNameLikeAndProject_NameOrderByNameAscVersionAsc(
-            "f%", "project1"))
+    when(featureSetRepository.findAllByNameLikeAndProject_NameOrderByNameAsc("f%", "project1"))
         .thenReturn(featureSets);
-    when(featureSetRepository.findAllByNameLikeAndProject_NameLikeOrderByNameAscVersionAsc(
-            "%", "%"))
+    when(featureSetRepository.findAllByNameLikeAndProject_NameLikeOrderByNameAsc("%", "%"))
         .thenReturn(featureSets);
 
     when(projectRepository.findAllByArchivedIsFalse())
         .thenReturn(Collections.singletonList(new Project("project1")));
     when(projectRepository.findById("project1")).thenReturn(Optional.of(new Project("project1")));
+    Project archivedProject = new Project("archivedproject");
+    archivedProject.setArchived(true);
+    when(projectRepository.findById(archivedProject.getName()))
+        .thenReturn(Optional.of(archivedProject));
 
     Store store1 = newDummyStore("SERVING");
     Store store2 = newDummyStore("WAREHOUSE");
@@ -163,14 +148,11 @@ public void setUp() {
   }
 
   @Test
-  public void shouldGetAllFeatureSetsIfOnlyWildcardsProvided() {
+  public void shouldGetAllFeatureSetsIfOnlyWildcardsProvided()
+      throws InvalidProtocolBufferException {
     ListFeatureSetsResponse actual =
         specService.listFeatureSets(
-            Filter.newBuilder()
-                .setFeatureSetName("*")
-                .setProject("*")
-                .setFeatureSetVersion("*")
-                .build());
+            Filter.newBuilder().setFeatureSetName("*").setProject("*").build());
     List list = new ArrayList<>();
     for (FeatureSet featureSet : featureSets) {
       FeatureSetProto.FeatureSet toProto = featureSet.toProto();
@@ -182,44 +164,11 @@ public void shouldGetAllFeatureSetsIfOnlyWildcardsProvided() {
   }
 
   @Test
-  public void listFeatureSetShouldFailIfFeatureSetProvidedWithoutProject() {
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage(
-        "Invalid listFeatureSetRequest, missing arguments. Must provide project, feature set name, and version.");
-    specService.listFeatureSets(
-        Filter.newBuilder().setFeatureSetName("f1").setFeatureSetVersion("1").build());
-  }
-
-  @Test
-  public void shouldGetAllFeatureSetsMatchingNameIfWildcardVersionProvided() {
+  public void shouldGetAllFeatureSetsMatchingNameWithWildcardSearch()
+      throws InvalidProtocolBufferException {
     ListFeatureSetsResponse actual =
         specService.listFeatureSets(
-            Filter.newBuilder()
-                .setProject("project1")
-                .setFeatureSetName("f1")
-                .setFeatureSetVersion("*")
-                .build());
-    List expectedFeatureSets =
-        featureSets.stream().filter(fs -> fs.getName().equals("f1")).collect(Collectors.toList());
-    List list = new ArrayList<>();
-    for (FeatureSet expectedFeatureSet : expectedFeatureSets) {
-      FeatureSetProto.FeatureSet toProto = expectedFeatureSet.toProto();
-      list.add(toProto);
-    }
-    ListFeatureSetsResponse expected =
-        ListFeatureSetsResponse.newBuilder().addAllFeatureSets(list).build();
-    assertThat(actual, equalTo(expected));
-  }
-
-  @Test
-  public void shouldGetAllFeatureSetsMatchingNameWithWildcardSearch() {
-    ListFeatureSetsResponse actual =
-        specService.listFeatureSets(
-            Filter.newBuilder()
-                .setProject("project1")
-                .setFeatureSetName("f*")
-                .setFeatureSetVersion("*")
-                .build());
+            Filter.newBuilder().setProject("project1").setFeatureSetName("f*").build());
     List expectedFeatureSets =
         featureSets.stream()
             .filter(fs -> fs.getName().startsWith("f"))
@@ -235,19 +184,12 @@ public void shouldGetAllFeatureSetsMatchingNameWithWildcardSearch() {
   }
 
   @Test
-  public void shouldGetAllFeatureSetsMatchingVersionIfNoComparator() {
+  public void shouldGetFeatureSetsByNameAndProject() throws InvalidProtocolBufferException {
     ListFeatureSetsResponse actual =
         specService.listFeatureSets(
-            Filter.newBuilder()
-                .setProject("project1")
-                .setFeatureSetName("f1")
-                .setFeatureSetVersion("1")
-                .build());
+            Filter.newBuilder().setProject("project1").setFeatureSetName("f1").build());
     List expectedFeatureSets =
-        featureSets.stream()
-            .filter(fs -> fs.getName().equals("f1"))
-            .filter(fs -> fs.getVersion() == 1)
-            .collect(Collectors.toList());
+        featureSets.stream().filter(fs -> fs.getName().equals("f1")).collect(Collectors.toList());
     List list = new ArrayList<>();
     for (FeatureSet expectedFeatureSet : expectedFeatureSets) {
       FeatureSetProto.FeatureSet toProto = expectedFeatureSet.toProto();
@@ -259,74 +201,19 @@ public void shouldGetAllFeatureSetsMatchingVersionIfNoComparator() {
   }
 
   @Test
-  public void shouldThrowExceptionIfGetAllFeatureSetsGivenVersionWithComparator() {
-    expectedException.expect(IllegalArgumentException.class);
-    specService.listFeatureSets(
-        Filter.newBuilder()
-            .setProject("project1")
-            .setFeatureSetName("f1")
-            .setFeatureSetVersion(">1")
-            .build());
-  }
-
-  @Test
-  public void shouldGetLatestFeatureSetGivenMissingVersionFilter() {
-    GetFeatureSetResponse actual =
-        specService.getFeatureSet(
-            GetFeatureSetRequest.newBuilder().setName("f1").setProject("project1").build());
-    FeatureSet expected = featureSets.get(2);
-    assertThat(actual.getFeatureSet(), equalTo(expected.toProto()));
-  }
-
-  @Test
-  public void shouldGetSpecificFeatureSetGivenSpecificVersionFilter() {
-    when(featureSetRepository.findFeatureSetByNameAndProject_NameAndVersion("f1", "project1", 2))
-        .thenReturn(featureSets.get(1));
-    GetFeatureSetResponse actual =
-        specService.getFeatureSet(
-            GetFeatureSetRequest.newBuilder()
-                .setProject("project1")
-                .setName("f1")
-                .setVersion(2)
-                .build());
-    FeatureSet expected = featureSets.get(1);
-    assertThat(actual.getFeatureSet(), equalTo(expected.toProto()));
-  }
-
-  @Test
-  public void shouldThrowExceptionGivenMissingFeatureSetName() {
+  public void shouldThrowExceptionGivenMissingFeatureSetName()
+      throws InvalidProtocolBufferException {
     expectedException.expect(IllegalArgumentException.class);
     expectedException.expectMessage("No feature set name provided");
-    specService.getFeatureSet(GetFeatureSetRequest.newBuilder().setVersion(2).build());
+    specService.getFeatureSet(GetFeatureSetRequest.newBuilder().build());
   }
 
   @Test
-  public void shouldThrowExceptionGivenMissingFeatureSet() {
+  public void shouldThrowExceptionGivenMissingFeatureSet() throws InvalidProtocolBufferException {
     expectedException.expect(RetrievalException.class);
-    expectedException.expectMessage(
-        "Feature set with name \"f1000\" and version \"2\" could not be found.");
+    expectedException.expectMessage("Feature set with name \"f1000\" could not be found.");
     specService.getFeatureSet(
-        GetFeatureSetRequest.newBuilder()
-            .setName("f1000")
-            .setProject("project1")
-            .setVersion(2)
-            .build());
-  }
-
-  @Test
-  public void shouldThrowRetrievalExceptionGivenInvalidFeatureSetVersionComparator() {
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage(
-        "Invalid listFeatureSetRequest. Version must be set to \"*\" if the project name and feature set name aren't set explicitly: \n"
-            + "feature_set_name: \"f1\"\n"
-            + "feature_set_version: \">1\"\n"
-            + "project: \"project1\"");
-    specService.listFeatureSets(
-        Filter.newBuilder()
-            .setProject("project1")
-            .setFeatureSetName("f1")
-            .setFeatureSetVersion(">1")
-            .build());
+        GetFeatureSetRequest.newBuilder().setName("f1000").setProject("project1").build());
   }
 
   @Test
@@ -361,9 +248,10 @@ public void shouldThrowRetrievalExceptionIfNoStoresFoundWithName() {
   }
 
   @Test
-  public void applyFeatureSetShouldReturnFeatureSetWithLatestVersionIfFeatureSetHasNotChanged() {
+  public void applyFeatureSetShouldReturnFeatureSetIfFeatureSetHasNotChanged()
+      throws InvalidProtocolBufferException {
     FeatureSetSpec incomingFeatureSetSpec =
-        featureSets.get(2).toProto().getSpec().toBuilder().clearVersion().build();
+        featureSets.get(0).toProto().getSpec().toBuilder().build();
 
     ApplyFeatureSetResponse applyFeatureSetResponse =
         specService.applyFeatureSet(
@@ -371,20 +259,19 @@ public void applyFeatureSetShouldReturnFeatureSetWithLatestVersionIfFeatureSetHa
 
     verify(featureSetRepository, times(0)).save(ArgumentMatchers.any(FeatureSet.class));
     assertThat(applyFeatureSetResponse.getStatus(), equalTo(Status.NO_CHANGE));
-    assertThat(applyFeatureSetResponse.getFeatureSet(), equalTo(featureSets.get(2).toProto()));
+    assertThat(applyFeatureSetResponse.getFeatureSet(), equalTo(featureSets.get(0).toProto()));
   }
 
   @Test
-  public void applyFeatureSetShouldApplyFeatureSetWithInitVersionIfNotExists() {
-    when(featureSetRepository.findAllByNameLikeAndProject_NameOrderByNameAscVersionAsc(
-            "f2", "project1"))
-        .thenReturn(Lists.newArrayList());
+  public void applyFeatureSetShouldApplyFeatureSetIfNotExists()
+      throws InvalidProtocolBufferException {
+    when(featureSetRepository.findFeatureSetByNameAndProject_Name("f2", "project1"))
+        .thenReturn(null);
 
-    FeatureSetProto.FeatureSet incomingFeatureSet =
-        newDummyFeatureSet("f2", 1, "project1").toProto();
+    FeatureSetProto.FeatureSet incomingFeatureSet = newDummyFeatureSet("f2", "project1").toProto();
 
     FeatureSetProto.FeatureSetSpec incomingFeatureSetSpec =
-        incomingFeatureSet.getSpec().toBuilder().clearVersion().build();
+        incomingFeatureSet.getSpec().toBuilder().build();
 
     ApplyFeatureSetResponse applyFeatureSetResponse =
         specService.applyFeatureSet(
@@ -393,23 +280,16 @@ public void applyFeatureSetShouldApplyFeatureSetWithInitVersionIfNotExists() {
 
     FeatureSetProto.FeatureSet expected =
         FeatureSetProto.FeatureSet.newBuilder()
-            .setSpec(
-                incomingFeatureSetSpec
-                    .toBuilder()
-                    .setVersion(1)
-                    .setSource(defaultSource.toProto())
-                    .build())
+            .setSpec(incomingFeatureSetSpec.toBuilder().setSource(defaultSource.toProto()).build())
             .build();
     assertThat(applyFeatureSetResponse.getStatus(), equalTo(Status.CREATED));
     assertThat(applyFeatureSetResponse.getFeatureSet().getSpec(), equalTo(expected.getSpec()));
-    assertThat(
-        applyFeatureSetResponse.getFeatureSet().getSpec().getVersion(),
-        equalTo(expected.getSpec().getVersion()));
   }
 
   @Test
-  public void applyFeatureSetShouldIncrementFeatureSetVersionIfAlreadyExists() {
-    FeatureSetProto.FeatureSet incomingFeatureSet = featureSets.get(2).toProto();
+  public void applyFeatureSetShouldUpdateAndSaveFeatureSetIfAlreadyExists()
+      throws InvalidProtocolBufferException {
+    FeatureSetProto.FeatureSet incomingFeatureSet = featureSets.get(0).toProto();
     incomingFeatureSet =
         incomingFeatureSet
             .toBuilder()
@@ -418,7 +298,6 @@ public void applyFeatureSetShouldIncrementFeatureSetVersionIfAlreadyExists() {
                 incomingFeatureSet
                     .getSpec()
                     .toBuilder()
-                    .clearVersion()
                     .addFeatures(
                         FeatureSpec.newBuilder().setName("feature2").setValueType(Enum.STRING))
                     .build())
@@ -429,43 +308,27 @@ public void applyFeatureSetShouldIncrementFeatureSetVersionIfAlreadyExists() {
             .toBuilder()
             .setMeta(incomingFeatureSet.getMeta().toBuilder().build())
             .setSpec(
-                incomingFeatureSet
-                    .getSpec()
-                    .toBuilder()
-                    .setVersion(4)
-                    .setSource(defaultSource.toProto())
-                    .build())
+                incomingFeatureSet.getSpec().toBuilder().setSource(defaultSource.toProto()).build())
             .build();
 
     ApplyFeatureSetResponse applyFeatureSetResponse =
         specService.applyFeatureSet(incomingFeatureSet);
     verify(projectRepository).saveAndFlush(ArgumentMatchers.any(Project.class));
-    assertThat(applyFeatureSetResponse.getStatus(), equalTo(Status.CREATED));
+    assertThat(applyFeatureSetResponse.getStatus(), equalTo(Status.UPDATED));
     assertEquals(
         FeatureSet.fromProto(applyFeatureSetResponse.getFeatureSet()),
         FeatureSet.fromProto(expected));
-    assertThat(
-        applyFeatureSetResponse.getFeatureSet().getSpec().getVersion(),
-        equalTo(expected.getSpec().getVersion()));
   }
 
   @Test
-  public void applyFeatureSetShouldNotCreateFeatureSetIfFieldsUnordered() {
+  public void applyFeatureSetShouldNotCreateFeatureSetIfFieldsUnordered()
+      throws InvalidProtocolBufferException {
 
-    Field f3f1 = new Field("f3f1", Enum.INT64);
-    Field f3f2 = new Field("f3f2", Enum.INT64);
-    Field f3e1 = new Field("f3e1", Enum.STRING);
-    FeatureSetProto.FeatureSet incomingFeatureSet =
-        (new FeatureSet(
-                "f3",
-                "project1",
-                5,
-                100L,
-                Arrays.asList(f3e1),
-                Arrays.asList(f3f2, f3f1),
-                defaultSource,
-                FeatureSetStatus.STATUS_READY))
-            .toProto();
+    FeatureSet featureSet = featureSets.get(1);
+    List features = Lists.newArrayList(featureSet.getFeatures());
+    Collections.shuffle(features);
+    featureSet.setFeatures(Set.copyOf(features));
+    FeatureSetProto.FeatureSet incomingFeatureSet = featureSet.toProto();
 
     ApplyFeatureSetResponse applyFeatureSetResponse =
         specService.applyFeatureSet(incomingFeatureSet);
@@ -481,6 +344,298 @@ public void applyFeatureSetShouldNotCreateFeatureSetIfFieldsUnordered() {
         equalTo(incomingFeatureSet.getSpec().getName()));
   }
 
+  @Test
+  public void applyFeatureSetShouldAcceptPresenceShapeAndDomainConstraints()
+      throws InvalidProtocolBufferException {
+    List entitySpecs = new ArrayList<>();
+    entitySpecs.add(EntitySpec.newBuilder().setName("entity1").setValueType(Enum.INT64).build());
+    entitySpecs.add(EntitySpec.newBuilder().setName("entity2").setValueType(Enum.INT64).build());
+    entitySpecs.add(EntitySpec.newBuilder().setName("entity3").setValueType(Enum.FLOAT).build());
+    entitySpecs.add(EntitySpec.newBuilder().setName("entity4").setValueType(Enum.STRING).build());
+    entitySpecs.add(EntitySpec.newBuilder().setName("entity5").setValueType(Enum.BOOL).build());
+
+    List featureSpecs = new ArrayList<>();
+    featureSpecs.add(
+        FeatureSpec.newBuilder()
+            .setName("feature1")
+            .setValueType(Enum.INT64)
+            .setPresence(FeaturePresence.getDefaultInstance())
+            .setShape(FixedShape.getDefaultInstance())
+            .setDomain("mydomain")
+            .build());
+    featureSpecs.add(
+        FeatureSpec.newBuilder()
+            .setName("feature2")
+            .setValueType(Enum.INT64)
+            .setGroupPresence(FeaturePresenceWithinGroup.getDefaultInstance())
+            .setValueCount(ValueCount.getDefaultInstance())
+            .setIntDomain(IntDomain.getDefaultInstance())
+            .build());
+    featureSpecs.add(
+        FeatureSpec.newBuilder()
+            .setName("feature3")
+            .setValueType(Enum.FLOAT)
+            .setPresence(FeaturePresence.getDefaultInstance())
+            .setValueCount(ValueCount.getDefaultInstance())
+            .setFloatDomain(FloatDomain.getDefaultInstance())
+            .build());
+    featureSpecs.add(
+        FeatureSpec.newBuilder()
+            .setName("feature4")
+            .setValueType(Enum.STRING)
+            .setPresence(FeaturePresence.getDefaultInstance())
+            .setValueCount(ValueCount.getDefaultInstance())
+            .setStringDomain(StringDomain.getDefaultInstance())
+            .build());
+    featureSpecs.add(
+        FeatureSpec.newBuilder()
+            .setName("feature5")
+            .setValueType(Enum.BOOL)
+            .setPresence(FeaturePresence.getDefaultInstance())
+            .setValueCount(ValueCount.getDefaultInstance())
+            .setBoolDomain(BoolDomain.getDefaultInstance())
+            .build());
+
+    FeatureSetSpec featureSetSpec =
+        FeatureSetSpec.newBuilder()
+            .setProject("project1")
+            .setName("featureSetWithConstraints")
+            .addAllEntities(entitySpecs)
+            .addAllFeatures(featureSpecs)
+            .build();
+    FeatureSetProto.FeatureSet featureSet =
+        FeatureSetProto.FeatureSet.newBuilder().setSpec(featureSetSpec).build();
+
+    ApplyFeatureSetResponse applyFeatureSetResponse = specService.applyFeatureSet(featureSet);
+    FeatureSetSpec appliedFeatureSetSpec = applyFeatureSetResponse.getFeatureSet().getSpec();
+
+    // appliedEntitySpecs needs to be sorted because the list returned by specService may not
+    // follow the order in the request
+    List appliedEntitySpecs = new ArrayList<>(appliedFeatureSetSpec.getEntitiesList());
+    appliedEntitySpecs.sort(Comparator.comparing(EntitySpec::getName));
+
+    // appliedFeatureSpecs needs to be sorted because the list returned by specService may not
+    // follow the order in the request
+    List appliedFeatureSpecs =
+        new ArrayList<>(appliedFeatureSetSpec.getFeaturesList());
+    appliedFeatureSpecs.sort(Comparator.comparing(FeatureSpec::getName));
+
+    assertEquals(appliedEntitySpecs, entitySpecs);
+    assertEquals(appliedFeatureSpecs, featureSpecs);
+  }
+
+  @Test
+  public void applyFeatureSetShouldUpdateFeatureSetWhenConstraintsAreUpdated()
+      throws InvalidProtocolBufferException {
+
+    // Map of constraint field name -> value, e.g. "shape" -> FixedShape object.
+    // If any of these fields are updated, SpecService should update the FeatureSet.
+    Map contraintUpdates = new HashMap<>();
+    contraintUpdates.put("presence", FeaturePresence.newBuilder().setMinFraction(0.5).build());
+    contraintUpdates.put(
+        "group_presence", FeaturePresenceWithinGroup.newBuilder().setRequired(true).build());
+    contraintUpdates.put("shape", FixedShape.getDefaultInstance());
+    contraintUpdates.put("value_count", ValueCount.newBuilder().setMin(2).build());
+    contraintUpdates.put("domain", "new_domain");
+    contraintUpdates.put("int_domain", IntDomain.newBuilder().setMax(100).build());
+    contraintUpdates.put("float_domain", FloatDomain.newBuilder().setMin(-0.5f).build());
+    contraintUpdates.put("string_domain", StringDomain.newBuilder().addValue("string1").build());
+    contraintUpdates.put("bool_domain", BoolDomain.newBuilder().setFalseValue("falsy").build());
+    contraintUpdates.put("struct_domain", StructDomain.getDefaultInstance());
+    contraintUpdates.put("natural_language_domain", NaturalLanguageDomain.getDefaultInstance());
+    contraintUpdates.put("image_domain", ImageDomain.getDefaultInstance());
+    contraintUpdates.put("mid_domain", MIDDomain.getDefaultInstance());
+    contraintUpdates.put("url_domain", URLDomain.getDefaultInstance());
+    contraintUpdates.put(
+        "time_domain", TimeDomain.newBuilder().setStringFormat("string_format").build());
+    contraintUpdates.put("time_of_day_domain", TimeOfDayDomain.getDefaultInstance());
+
+    for (Entry constraint : contraintUpdates.entrySet()) {
+      FeatureSet featureSet = newDummyFeatureSet("constraints", "project1");
+      FeatureSetProto.FeatureSet existingFeatureSet = featureSet.toProto();
+      when(featureSetRepository.findFeatureSetByNameAndProject_Name("constraints", "project1"))
+          .thenReturn(featureSet);
+      String name = constraint.getKey();
+      Object value = constraint.getValue();
+      FeatureSpec newFeatureSpec =
+          existingFeatureSet
+              .getSpec()
+              .getFeatures(0)
+              .toBuilder()
+              .setField(FeatureSpec.getDescriptor().findFieldByName(name), value)
+              .build();
+      FeatureSetSpec newFeatureSetSpec =
+          existingFeatureSet.getSpec().toBuilder().setFeatures(0, newFeatureSpec).build();
+      FeatureSetProto.FeatureSet newFeatureSet =
+          existingFeatureSet.toBuilder().setSpec(newFeatureSetSpec).build();
+
+      ApplyFeatureSetResponse response = specService.applyFeatureSet(newFeatureSet);
+
+      assertEquals(
+          "Response should have CREATED status when field '" + name + "' is updated",
+          Status.UPDATED,
+          response.getStatus());
+      assertEquals(
+          "Feature should have field '" + name + "' set correctly",
+          constraint.getValue(),
+          response
+              .getFeatureSet()
+              .getSpec()
+              .getFeatures(0)
+              .getField(FeatureSpec.getDescriptor().findFieldByName(name)));
+    }
+  }
+
+  @Test
+  public void applyFeatureSetShouldCreateProjectWhenNotAlreadyExists()
+      throws InvalidProtocolBufferException {
+    Feature f3f1 = TestObjectFactory.CreateFeature("f3f1", Enum.INT64);
+    Feature f3f2 = TestObjectFactory.CreateFeature("f3f2", Enum.INT64);
+    Entity f3e1 = TestObjectFactory.CreateEntity("f3e1", Enum.STRING);
+    FeatureSetProto.FeatureSet incomingFeatureSet =
+        TestObjectFactory.CreateFeatureSet(
+                "f3", "project", Arrays.asList(f3e1), Arrays.asList(f3f2, f3f1))
+            .toProto();
+
+    ApplyFeatureSetResponse applyFeatureSetResponse =
+        specService.applyFeatureSet(incomingFeatureSet);
+    assertThat(applyFeatureSetResponse.getStatus(), equalTo(Status.CREATED));
+    assertThat(
+        applyFeatureSetResponse.getFeatureSet().getSpec().getProject(),
+        equalTo(incomingFeatureSet.getSpec().getProject()));
+  }
+
+  @Test
+  public void applyFeatureSetShouldUsedDefaultProjectIfUnspecified()
+      throws InvalidProtocolBufferException {
+    Feature f3f1 = TestObjectFactory.CreateFeature("f3f1", Enum.INT64);
+    Feature f3f2 = TestObjectFactory.CreateFeature("f3f2", Enum.INT64);
+    Entity f3e1 = TestObjectFactory.CreateEntity("f3e1", Enum.STRING);
+
+    // In protov3, unspecified project defaults to ""
+    FeatureSetProto.FeatureSet incomingFeatureSet =
+        TestObjectFactory.CreateFeatureSet("f3", "", Arrays.asList(f3e1), Arrays.asList(f3f2, f3f1))
+            .toProto();
+    ApplyFeatureSetResponse applyFeatureSetResponse =
+        specService.applyFeatureSet(incomingFeatureSet);
+    assertThat(applyFeatureSetResponse.getStatus(), equalTo(Status.CREATED));
+
+    assertThat(
+        applyFeatureSetResponse.getFeatureSet().getSpec().getProject(),
+        equalTo(Project.DEFAULT_NAME));
+  }
+
+  @Test
+  public void applyFeatureSetShouldFailWhenProjectIsArchived()
+      throws InvalidProtocolBufferException {
+    Feature f3f1 = TestObjectFactory.CreateFeature("f3f1", Enum.INT64);
+    Feature f3f2 = TestObjectFactory.CreateFeature("f3f2", Enum.INT64);
+    Entity f3e1 = TestObjectFactory.CreateEntity("f3e1", Enum.STRING);
+    FeatureSetProto.FeatureSet incomingFeatureSet =
+        TestObjectFactory.CreateFeatureSet(
+                "f3", "archivedproject", Arrays.asList(f3e1), Arrays.asList(f3f2, f3f1))
+            .toProto();
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Project is archived");
+    specService.applyFeatureSet(incomingFeatureSet);
+  }
+
+  @Test
+  public void applyFeatureSetShouldAcceptFeatureLabels() throws InvalidProtocolBufferException {
+    List entitySpecs = new ArrayList<>();
+    entitySpecs.add(EntitySpec.newBuilder().setName("entity1").setValueType(Enum.INT64).build());
+
+    Map featureLabels0 =
+        new HashMap<>() {
+          {
+            put("label1", "feast1");
+          }
+        };
+
+    Map featureLabels1 =
+        new HashMap<>() {
+          {
+            put("label1", "feast1");
+            put("label2", "feast2");
+          }
+        };
+
+    List> featureLabels = new ArrayList<>();
+    featureLabels.add(featureLabels0);
+    featureLabels.add(featureLabels1);
+
+    List featureSpecs = new ArrayList<>();
+    featureSpecs.add(
+        FeatureSpec.newBuilder()
+            .setName("feature1")
+            .setValueType(Enum.INT64)
+            .putAllLabels(featureLabels.get(0))
+            .build());
+    featureSpecs.add(
+        FeatureSpec.newBuilder()
+            .setName("feature2")
+            .setValueType(Enum.INT64)
+            .putAllLabels(featureLabels.get(1))
+            .build());
+
+    FeatureSetSpec featureSetSpec =
+        FeatureSetSpec.newBuilder()
+            .setProject("project1")
+            .setName("featureSetWithConstraints")
+            .addAllEntities(entitySpecs)
+            .addAllFeatures(featureSpecs)
+            .build();
+    FeatureSetProto.FeatureSet featureSet =
+        FeatureSetProto.FeatureSet.newBuilder().setSpec(featureSetSpec).build();
+
+    ApplyFeatureSetResponse applyFeatureSetResponse = specService.applyFeatureSet(featureSet);
+    FeatureSetSpec appliedFeatureSetSpec = applyFeatureSetResponse.getFeatureSet().getSpec();
+
+    // appliedEntitySpecs needs to be sorted because the list returned by specService may not
+    // follow the order in the request
+    List appliedEntitySpecs = new ArrayList<>(appliedFeatureSetSpec.getEntitiesList());
+    appliedEntitySpecs.sort(Comparator.comparing(EntitySpec::getName));
+
+    // appliedFeatureSpecs needs to be sorted because the list returned by specService may not
+    // follow the order in the request
+    List appliedFeatureSpecs =
+        new ArrayList<>(appliedFeatureSetSpec.getFeaturesList());
+    appliedFeatureSpecs.sort(Comparator.comparing(FeatureSpec::getName));
+
+    var featureSpecsLabels =
+        featureSpecs.stream().map(e -> e.getLabelsMap()).collect(Collectors.toList());
+    assertEquals(appliedEntitySpecs, entitySpecs);
+    assertEquals(appliedFeatureSpecs, featureSpecs);
+    assertEquals(featureSpecsLabels, featureLabels);
+  }
+
+  @Test
+  public void applyFeatureSetShouldAcceptFeatureSetLabels() throws InvalidProtocolBufferException {
+    Map featureSetLabels =
+        new HashMap<>() {
+          {
+            put("description", "My precious feature set");
+          }
+        };
+
+    FeatureSetSpec featureSetSpec =
+        FeatureSetSpec.newBuilder()
+            .setProject("project1")
+            .setName("preciousFeatureSet")
+            .putAllLabels(featureSetLabels)
+            .build();
+    FeatureSetProto.FeatureSet featureSet =
+        FeatureSetProto.FeatureSet.newBuilder().setSpec(featureSetSpec).build();
+
+    ApplyFeatureSetResponse applyFeatureSetResponse = specService.applyFeatureSet(featureSet);
+    FeatureSetSpec appliedFeatureSetSpec = applyFeatureSetResponse.getFeatureSet().getSpec();
+
+    var appliedLabels = appliedFeatureSetSpec.getLabelsMap();
+
+    assertEquals(featureSetLabels, appliedLabels);
+  }
+
   @Test
   public void shouldUpdateStoreIfConfigChanges() throws InvalidProtocolBufferException {
     when(storeRepository.findById("SERVING")).thenReturn(Optional.of(stores.get(0)));
@@ -489,8 +644,7 @@ public void shouldUpdateStoreIfConfigChanges() throws InvalidProtocolBufferExcep
             .setName("SERVING")
             .setType(StoreType.REDIS)
             .setRedisConfig(RedisConfig.newBuilder())
-            .addSubscriptions(
-                Subscription.newBuilder().setProject("project1").setName("a").setVersion(">1"))
+            .addSubscriptions(Subscription.newBuilder().setProject("project1").setName("a"))
             .build();
     UpdateStoreResponse actual =
         specService.updateStore(UpdateStoreRequest.newBuilder().setStore(newStore).build());
@@ -521,25 +675,35 @@ public void shouldDoNothingIfNoChange() throws InvalidProtocolBufferException {
   }
 
   @Test
-  public void shouldFailIfGetFeatureSetWithoutProject() {
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("No project provided");
-    specService.getFeatureSet(GetFeatureSetRequest.newBuilder().setName("f1").build());
+  public void getOrListFeatureSetShouldUseDefaultProjectIfProjectUnspecified()
+      throws InvalidProtocolBufferException {
+    when(featureSetRepository.findFeatureSetByNameAndProject_Name("f4", Project.DEFAULT_NAME))
+        .thenReturn(featureSets.get(3));
+    FeatureSet expected = featureSets.get(3);
+    // check getFeatureSet()
+    GetFeatureSetResponse getResponse =
+        specService.getFeatureSet(GetFeatureSetRequest.newBuilder().setName("f4").build());
+    assertThat(getResponse.getFeatureSet(), equalTo(expected.toProto()));
+
+    // check listFeatureSets()
+    ListFeatureSetsResponse listResponse =
+        specService.listFeatureSets(Filter.newBuilder().setFeatureSetName("f4").build());
+    assertThat(listResponse.getFeatureSetsList(), equalTo(Arrays.asList(expected.toProto())));
   }
 
-  private FeatureSet newDummyFeatureSet(String name, int version, String project) {
-    Field feature = new Field("feature", Enum.INT64);
-    Field entity = new Field("entity", Enum.STRING);
+  private FeatureSet newDummyFeatureSet(String name, String project) {
+    FeatureSpec f1 =
+        FeatureSpec.newBuilder()
+            .setName("feature")
+            .setValueType(Enum.STRING)
+            .putLabels("key", "value")
+            .build();
+    Feature feature = Feature.fromProto(f1);
+    Entity entity = TestObjectFactory.CreateEntity("entity", Enum.STRING);
+
     FeatureSet fs =
-        new FeatureSet(
-            name,
-            project,
-            version,
-            100L,
-            Arrays.asList(entity),
-            Arrays.asList(feature),
-            defaultSource,
-            FeatureSetStatus.STATUS_READY);
+        TestObjectFactory.CreateFeatureSet(
+            name, project, Arrays.asList(entity), Arrays.asList(feature));
     fs.setCreated(Date.from(Instant.ofEpochSecond(10L)));
     return fs;
   }
@@ -549,7 +713,7 @@ private Store newDummyStore(String name) {
     Store store = new Store();
     store.setName(name);
     store.setType(StoreType.REDIS.toString());
-    store.setSubscriptions("*:*:*");
+    store.setSubscriptions("*:*");
     store.setConfig(RedisConfig.newBuilder().setPort(6379).build().toByteArray());
     return store;
   }
diff --git a/core/src/test/java/feast/core/service/TestObjectFactory.java b/core/src/test/java/feast/core/service/TestObjectFactory.java
new file mode 100644
index 00000000000..40c379d3cf7
--- /dev/null
+++ b/core/src/test/java/feast/core/service/TestObjectFactory.java
@@ -0,0 +1,62 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright 2018-2020 The Feast Authors
+ *
+ * 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 feast.core.service;
+
+import feast.core.model.Entity;
+import feast.core.model.Feature;
+import feast.core.model.FeatureSet;
+import feast.core.model.Source;
+import feast.proto.core.FeatureSetProto;
+import feast.proto.core.SourceProto;
+import feast.proto.types.ValueProto;
+import java.util.HashMap;
+import java.util.List;
+
+public class TestObjectFactory {
+
+  public static Source defaultSource =
+      new Source(
+          SourceProto.SourceType.KAFKA,
+          SourceProto.KafkaSourceConfig.newBuilder()
+              .setBootstrapServers("kafka:9092")
+              .setTopic("my-topic")
+              .build(),
+          true);
+
+  public static FeatureSet CreateFeatureSet(
+      String name, String project, List entities, List features) {
+    return new FeatureSet(
+        name,
+        project,
+        100L,
+        entities,
+        features,
+        defaultSource,
+        new HashMap<>(),
+        FeatureSetProto.FeatureSetStatus.STATUS_READY);
+  }
+
+  public static Feature CreateFeature(String name, ValueProto.ValueType.Enum valueType) {
+    return Feature.fromProto(
+        FeatureSetProto.FeatureSpec.newBuilder().setName(name).setValueType(valueType).build());
+  }
+
+  public static Entity CreateEntity(String name, ValueProto.ValueType.Enum valueType) {
+    return Entity.fromProto(
+        FeatureSetProto.EntitySpec.newBuilder().setName(name).setValueType(valueType).build());
+  }
+}
diff --git a/core/src/test/java/feast/core/util/TypeConversionTest.java b/core/src/test/java/feast/core/util/TypeConversionTest.java
index 75548f34653..02f0a7cee45 100644
--- a/core/src/test/java/feast/core/util/TypeConversionTest.java
+++ b/core/src/test/java/feast/core/util/TypeConversionTest.java
@@ -18,8 +18,7 @@
 
 import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
 import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
 
 import com.google.protobuf.Timestamp;
 import java.util.*;
@@ -70,6 +69,12 @@ public void convertMapToJsonStringShouldReturnJsonStringForGivenMap() {
         TypeConversion.convertMapToJsonString(input), hasJsonPath("$.key", equalTo("value")));
   }
 
+  @Test
+  public void convertMapToJsonStringShouldReturnEmptyJsonForAnEmptyMap() {
+    Map input = new HashMap<>();
+    assertThat(TypeConversion.convertMapToJsonString(input), equalTo("{}"));
+  }
+
   @Test
   public void convertJsonStringToArgsShouldReturnCorrectListOfArgs() {
     Map input = new HashMap<>();
diff --git a/core/src/test/java/feast/core/validators/FeatureSetValidatorTest.java b/core/src/test/java/feast/core/validators/FeatureSetValidatorTest.java
new file mode 100644
index 00000000000..155a52d1005
--- /dev/null
+++ b/core/src/test/java/feast/core/validators/FeatureSetValidatorTest.java
@@ -0,0 +1,87 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright 2018-2020 The Feast Authors
+ *
+ * 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 feast.core.validators;
+
+import feast.proto.core.FeatureSetProto;
+import feast.proto.types.ValueProto;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class FeatureSetValidatorTest {
+
+  @Rule public final ExpectedException expectedException = ExpectedException.none();
+
+  @Test
+  public void shouldThrowExceptionForFeatureLabelsWithAnEmptyKey() {
+    Map featureLabels =
+        new HashMap<>() {
+          {
+            put("", "empty_key");
+          }
+        };
+
+    List featureSpecs = new ArrayList<>();
+    featureSpecs.add(
+        FeatureSetProto.FeatureSpec.newBuilder()
+            .setName("feature1")
+            .setValueType(ValueProto.ValueType.Enum.INT64)
+            .putAllLabels(featureLabels)
+            .build());
+
+    FeatureSetProto.FeatureSetSpec featureSetSpec =
+        FeatureSetProto.FeatureSetSpec.newBuilder()
+            .setProject("project1")
+            .setName("featureSetWithConstraints")
+            .addAllFeatures(featureSpecs)
+            .build();
+    FeatureSetProto.FeatureSet featureSet =
+        FeatureSetProto.FeatureSet.newBuilder().setSpec(featureSetSpec).build();
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Feature label keys must not be empty");
+    FeatureSetValidator.validateSpec(featureSet);
+  }
+
+  @Test
+  public void shouldThrowExceptionForFeatureSetLabelsWithAnEmptyKey() {
+
+    Map featureSetLabels =
+        new HashMap<>() {
+          {
+            put("", "empty_key");
+          }
+        };
+
+    FeatureSetProto.FeatureSetSpec featureSetSpec =
+        FeatureSetProto.FeatureSetSpec.newBuilder()
+            .setProject("project1")
+            .setName("featureSetWithConstraints")
+            .putAllLabels(featureSetLabels)
+            .build();
+    FeatureSetProto.FeatureSet featureSet =
+        FeatureSetProto.FeatureSet.newBuilder().setSpec(featureSetSpec).build();
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Feature set label keys must not be empty");
+    FeatureSetValidator.validateSpec(featureSet);
+  }
+}
diff --git a/datatypes/java/README.md b/datatypes/java/README.md
new file mode 100644
index 00000000000..f87a50f1bd1
--- /dev/null
+++ b/datatypes/java/README.md
@@ -0,0 +1,55 @@
+Feast Data Types for Java
+=========================
+
+This module produces Java class files for Feast's data type and gRPC service
+definitions, from Protobuf IDL. These are used across Feast components for wire
+interchange, contracts, etc.
+
+End users of Feast will be best served by our Java SDK which adds higher-level
+conveniences, but the data types are published independently for custom needs,
+without any additional dependencies the SDK may add.
+
+Dependency Coordinates
+----------------------
+
+```xml
+
+  dev.feast
+  datatypes-java
+  0.4.0-SNAPSHOT
+
+```
+
+Use the version corresponding to the Feast release you have deployed in your
+environment—see the [Feast release notes] for details.
+
+[Feast release notes]: ../../CHANGELOG.md
+
+Using the `.proto` Definitions
+------------------------------
+
+The `.proto` definitions are packaged as resources within the Maven artifact,
+which may be useful to `include` them in dependent Protobuf definitions in a
+downstream project, or for other JVM languages to consume from their builds to
+generate more idiomatic bindings.
+
+Google's Gradle plugin, for instance, [can use protos in dependencies][Gradle]
+either for `include` or to compile with a different `protoc` plugin than Java.
+
+[sbt-protoc] offers similar functionality for sbt/Scala.
+
+[Gradle]: https://github.com/google/protobuf-gradle-plugin#protos-in-dependencies
+[sbt-protoc]: https://github.com/thesamet/sbt-protoc
+
+Releases
+--------
+
+The module is published to Maven Central upon each release of Feast (since
+v0.3.7).
+
+For developers, the publishing process is automated along with the Java SDK by
+[the `publish-java-sdk` build task in Prow][prow task], where you can see how
+it works. Artifacts are staged to Sonatype where a maintainer needs to take a
+release action for them to go live on Maven Central.
+
+[prow task]: https://github.com/feast-dev/feast/blob/17e7dca8238aae4dcbf0ff9f0db5d80ef8e035cf/.prow/config.yaml#L166-L192
diff --git a/datatypes/java/pom.xml b/datatypes/java/pom.xml
new file mode 100644
index 00000000000..a127853258e
--- /dev/null
+++ b/datatypes/java/pom.xml
@@ -0,0 +1,120 @@
+
+
+
+    4.0.0
+
+    Feast Data Types for Java
+    
+        Data types and service contracts used throughout Feast components and
+        their interchanges. These are generated from Protocol Buffers and gRPC
+        definitions included in the package.
+    
+    datatypes-java
+
+    
+      dev.feast
+      feast-parent
+      ${revision}
+      ../..
+    
+
+    
+      
+        
+          org.apache.maven.plugins
+          maven-compiler-plugin
+          
+            
+            8
+          
+        
+
+        
+          org.apache.maven.plugins
+          maven-dependency-plugin
+          
+            
+            
+              javax.annotation
+            
+          
+        
+
+        
+          org.xolstice.maven.plugins
+          protobuf-maven-plugin
+          
+            true
+            
+                com.google.protobuf:protoc:${protocVersion}:exe:${os.detected.classifier}
+            
+            grpc-java
+            
+                io.grpc:protoc-gen-grpc-java:${grpcVersion}:exe:${os.detected.classifier}
+            
+          
+          
+            
+              
+                compile
+                compile-custom
+                test-compile
+              
+            
+          
+        
+      
+    
+
+    
+      
+      
+        com.google.guava
+        guava
+      
+      
+        com.google.protobuf
+        protobuf-java
+      
+
+      
+        io.grpc
+        grpc-core
+      
+      
+        io.grpc
+        grpc-protobuf
+      
+      
+        io.grpc
+        grpc-services
+      
+      
+        io.grpc
+        grpc-stub
+      
+
+      
+          javax.annotation
+          javax.annotation-api
+      
+    
+
+
diff --git a/sdk/java/src/main/proto/feast b/datatypes/java/src/main/proto/feast
similarity index 100%
rename from sdk/java/src/main/proto/feast
rename to datatypes/java/src/main/proto/feast
diff --git a/datatypes/java/src/main/proto/tensorflow_metadata b/datatypes/java/src/main/proto/tensorflow_metadata
new file mode 120000
index 00000000000..a633bb850f3
--- /dev/null
+++ b/datatypes/java/src/main/proto/tensorflow_metadata
@@ -0,0 +1 @@
+../../../../../protos/tensorflow_metadata
\ No newline at end of file
diff --git a/datatypes/java/src/main/proto/third_party b/datatypes/java/src/main/proto/third_party
new file mode 120000
index 00000000000..f015f8477d1
--- /dev/null
+++ b/datatypes/java/src/main/proto/third_party
@@ -0,0 +1 @@
+../../../../../protos/third_party
\ No newline at end of file
diff --git a/docs/.gitbook/assets/blank-diagram-4.svg b/docs/.gitbook/assets/blank-diagram-4.svg
new file mode 100644
index 00000000000..fb5e0659e55
--- /dev/null
+++ b/docs/.gitbook/assets/blank-diagram-4.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/.gitbook/assets/image (1).png b/docs/.gitbook/assets/image (1).png
new file mode 100644
index 00000000000..090fdabf792
Binary files /dev/null and b/docs/.gitbook/assets/image (1).png differ
diff --git a/docs/.gitbook/assets/image (2).png b/docs/.gitbook/assets/image (2).png
new file mode 100644
index 00000000000..d3b359a5988
Binary files /dev/null and b/docs/.gitbook/assets/image (2).png differ
diff --git a/docs/.gitbook/assets/image (3).png b/docs/.gitbook/assets/image (3).png
new file mode 100644
index 00000000000..2442410112f
Binary files /dev/null and b/docs/.gitbook/assets/image (3).png differ
diff --git a/docs/.gitbook/assets/image.png b/docs/.gitbook/assets/image.png
new file mode 100644
index 00000000000..273924d8cdb
Binary files /dev/null and b/docs/.gitbook/assets/image.png differ
diff --git a/docs/README.md b/docs/README.md
index 62d152ca1e0..9593e9f26e9 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,4 +1,4 @@
-# Overview
+# What is Feast?
 
 Feast \(**Fea**ture **St**ore\) is a tool for managing and serving machine learning features.
 
diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md
index 535fbe6081c..345b49fe0d1 100644
--- a/docs/SUMMARY.md
+++ b/docs/SUMMARY.md
@@ -1,21 +1,45 @@
 # Table of contents
 
-* [Overview](README.md)
-* [Why Feast?](why-feast.md)
-* [Concepts](concepts.md)
-* [Getting Help](getting-help.md)
-* [Contributing](contributing.md)
+* [What is Feast?](README.md)
 
-## Getting Started
+## Introduction
 
-* [Installing Feast](getting-started/installing-feast.md)
-* [Using Feast](https://github.com/gojek/feast/blob/master/examples/basic/basic.ipynb)
+* [Why Feast?](introduction/why-feast.md)
+* [Getting Help](introduction/getting-help.md)
+* [Roadmap](introduction/roadmap.md)
+* [Changelog](https://github.com/feast-dev/feast/blob/master/CHANGELOG.md)
+
+## Concepts
+
+* [Concepts](concepts/concepts.md)
+
+## Installation
+
+* [Overview](installation/overview.md)
+* [Docker Compose](installation/docker-compose.md)
+* [Google Kubernetes Engine \(GKE\)](installation/gke.md)
+
+## Tutorials
+
+* [Basic](https://github.com/feast-dev/feast/blob/master/examples/basic/basic.ipynb)
+* [Churn Prediction \(XGBoost\)](https://github.com/feast-dev/feast/blob/master/examples/feast-xgboost-churn-prediction-tutorial/Telecom%20Customer%20Churn%20Prediction%20%28with%20Feast%20and%20XGBoost%29.ipynb)
+
+## Administration
+
+* [Troubleshooting](administration/troubleshooting.md)
 
 ## Reference
 
 * [Python SDK](https://api.docs.feast.dev/python/)
-* [Go SDK](https://godoc.org/github.com/gojek/feast/sdk/go)
+* [Go SDK](https://godoc.org/github.com/feast-dev/feast/sdk/go)
 * [gRPC Types](https://api.docs.feast.dev/grpc/feast.types.pb.html)
 * [Core gRPC API](https://api.docs.feast.dev/grpc/feast.core.pb.html)
 * [Serving gRPC API](https://api.docs.feast.dev/grpc/feast.serving.pb.html)
 
+## Contributing
+
+* [Contribution Process](contributing/contributing.md)
+* [Development Guide](contributing/development-guide.md)
+* [Style Guide](contributing/style-guide.md)
+* [Release Process](contributing/release-process.md)
+
diff --git a/docs/administration/troubleshooting.md b/docs/administration/troubleshooting.md
new file mode 100644
index 00000000000..7924a33e733
--- /dev/null
+++ b/docs/administration/troubleshooting.md
@@ -0,0 +1,168 @@
+# Troubleshooting
+
+If at any point in time you cannot resolve a problem, please see the [Getting Help](https://github.com/feast-dev/feast/tree/75f3b783e5a7c5e0217a3020422548fb0d0ce0bf/docs/getting-help.md) section for reaching out to the Feast community.
+
+## How can I verify that all services are operational?
+
+### Docker Compose
+
+The containers should be in an `up` state:
+
+```text
+docker ps
+```
+
+```text
+CONTAINER ID        IMAGE                                  COMMAND                  CREATED             STATUS              PORTS                                            NAMES
+d7447205bced        jupyter/datascience-notebook:latest    "tini -g -- start-no…"   2 minutes ago       Up 2 minutes        0.0.0.0:8888->8888/tcp                           feast_jupyter_1
+8e49dbe81b92        gcr.io/kf-feast/feast-serving:latest   "java -Xms1024m -Xmx…"   2 minutes ago       Up 5 seconds        0.0.0.0:6567->6567/tcp                           feast_batch-serving_1
+b859494bd33a        gcr.io/kf-feast/feast-serving:latest   "java -jar /opt/feas…"   2 minutes ago       Up About a minute   0.0.0.0:6566->6566/tcp                           feast_online-serving_1
+5c4962811767        gcr.io/kf-feast/feast-core:latest      "java -jar /opt/feas…"   2 minutes ago       Up 2 minutes        0.0.0.0:6565->6565/tcp                           feast_core_1
+1ba7239e0ae0        confluentinc/cp-kafka:5.2.1            "/etc/confluent/dock…"   2 minutes ago       Up 2 minutes        0.0.0.0:9092->9092/tcp, 0.0.0.0:9094->9094/tcp   feast_kafka_1
+e2779672735c        confluentinc/cp-zookeeper:5.2.1        "/etc/confluent/dock…"   2 minutes ago       Up 2 minutes        2181/tcp, 2888/tcp, 3888/tcp                     feast_zookeeper_1
+39ac26f5c709        postgres:12-alpine                     "docker-entrypoint.s…"   2 minutes ago       Up 2 minutes        5432/tcp                                         feast_db_1
+3c4ee8616096        redis:5-alpine                         "docker-entrypoint.s…"   2 minutes ago       Up 2 minutes        0.0.0.0:6379->6379/tcp                           feast_redis_1
+```
+
+### Google Kubernetes Engine
+
+All services should either be in a `running` state or `complete`state:
+
+```text
+kubectl get pods
+```
+
+```text
+NAME                                                READY   STATUS      RESTARTS   AGE
+feast-feast-core-5ff566f946-4wlbh                   1/1     Running     1          32m
+feast-feast-serving-batch-848d74587b-96hq6          1/1     Running     2          32m
+feast-feast-serving-online-df69755d5-fml8v          1/1     Running     2          32m
+feast-kafka-0                                       1/1     Running     1          32m
+feast-kafka-1                                       1/1     Running     0          30m
+feast-kafka-2                                       1/1     Running     0          29m
+feast-kafka-config-3e860262-zkzr8                   0/1     Completed   0          32m
+feast-postgresql-0                                  1/1     Running     0          32m
+feast-prometheus-statsd-exporter-554db85b8d-r4hb8   1/1     Running     0          32m
+feast-redis-master-0                                1/1     Running     0          32m
+feast-zookeeper-0                                   1/1     Running     0          32m
+feast-zookeeper-1                                   1/1     Running     0          32m
+feast-zookeeper-2                                   1/1     Running     0          31m
+```
+
+## How can I verify that I can connect to all services?
+
+First find the `IP:Port` combination of your services.
+
+### **Docker Compose \(from inside the docker cluster\)**
+
+You will probably need to connect using the hostnames of services and standard Feast ports:
+
+```bash
+export FEAST_CORE_URL=core:6565
+export FEAST_ONLINE_SERVING_URL=online-serving:6566
+export FEAST_BATCH_SERVING_URL=batch-serving:6567
+```
+
+### **Docker Compose \(from outside the docker cluster\)**
+
+You will probably need to connect using `localhost` and standard ports:
+
+```bash
+export FEAST_CORE_URL=localhost:6565
+export FEAST_ONLINE_SERVING_URL=localhost:6566
+export FEAST_BATCH_SERVING_URL=localhost:6567
+```
+
+### **Google Kubernetes Engine \(GKE\)**
+
+You will need to find the external IP of one of the nodes as well as the NodePorts. Please make sure that your firewall is open for these ports:
+
+```bash
+export FEAST_IP=$(kubectl describe nodes | grep ExternalIP | awk '{print $2}' | head -n 1)
+export FEAST_CORE_URL=${FEAST_IP}:32090
+export FEAST_ONLINE_SERVING_URL=${FEAST_IP}:32091
+export FEAST_BATCH_SERVING_URL=${FEAST_IP}:32092
+```
+
+`netcat`, `telnet`, or even `curl` can be used to test whether all services are available and ports are open, but `grpc_cli` is the most powerful. It can be installed from [here](https://github.com/grpc/grpc/blob/master/doc/command_line_tool.md).
+
+### Testing Feast Core:
+
+```bash
+grpc_cli ls ${FEAST_CORE_URL} feast.core.CoreService
+```
+
+```text
+GetFeastCoreVersion
+GetFeatureSet
+ListFeatureSets
+ListStores
+ApplyFeatureSet
+UpdateStore
+CreateProject
+ArchiveProject
+ListProjects
+```
+
+### Testing Feast Batch Serving and Online Serving
+
+```bash
+grpc_cli ls ${FEAST_BATCH_SERVING_URL} feast.serving.ServingService
+```
+
+```text
+GetFeastServingInfo
+GetOnlineFeatures
+GetBatchFeatures
+GetJob
+```
+
+```bash
+grpc_cli ls ${FEAST_ONLINE_SERVING_URL} feast.serving.ServingService
+```
+
+```text
+GetFeastServingInfo
+GetOnlineFeatures
+GetBatchFeatures
+GetJob
+```
+
+## How can I print logs from the Feast Services?
+
+Feast will typically have three services that you need to monitor if something goes wrong.
+
+* Feast Core
+* Feast Serving \(Online\)
+* Feast Serving \(Batch\)
+
+In order to print the logs from these services, please run the commands below.
+
+### Docker Compose
+
+```text
+ docker logs -f feast_core_1
+```
+
+```text
+docker logs -f feast_batch-serving_1
+```
+
+```text
+docker logs -f feast_online-serving_1
+```
+
+### Google Kubernetes Engine
+
+```text
+kubectl logs $(kubectl get pods | grep feast-core | awk '{print $1}')
+```
+
+```text
+kubectl logs $(kubectl get pods | grep feast-serving-batch | awk '{print $1}')
+```
+
+```text
+kubectl logs $(kubectl get pods | grep feast-serving-online | awk '{print $1}')
+```
+
diff --git a/docs/concepts.md b/docs/concepts/concepts.md
similarity index 96%
rename from docs/concepts.md
rename to docs/concepts/concepts.md
index 860515c3699..e54bd488d6a 100644
--- a/docs/concepts.md
+++ b/docs/concepts/concepts.md
@@ -2,7 +2,7 @@
 
 ## Architecture
 
-![Logical diagram of a typical Feast deployment](.gitbook/assets/basic-architecture-diagram.svg)
+![Logical diagram of a typical Feast deployment](../.gitbook/assets/basic-architecture-diagram%20%282%29.svg)
 
 The core components of a Feast deployment are
 
@@ -106,13 +106,13 @@ Feast supports the following types for feature values
 * DOUBLE
 * FLOAT
 * BOOL
-* BYTES_LIST
-* STRING_LIST
-* INT32_LIST
-* INT64_LIST
-* DOUBLE_LIST
-* FLOAT_LIST
-* BOOL_LIST
+* BYTES\_LIST
+* STRING\_LIST
+* INT32\_LIST
+* INT64\_LIST
+* DOUBLE\_LIST
+* FLOAT\_LIST
+* BOOL\_LIST
 
 ## Glossary
 
diff --git a/docs/contributing.md b/docs/contributing.md
deleted file mode 100644
index 38caffd654b..00000000000
--- a/docs/contributing.md
+++ /dev/null
@@ -1,341 +0,0 @@
-# Contributing
-
-## Getting Started
-
-The following guide will help you quickly run Feast in your local machine.
-
-The main components of Feast are:
-
-* **Feast Core** handles FeatureSpec registration, starts and monitors Ingestion 
-
-  jobs and ensures that Feast internal metadata is consistent.
-
-* **Feast Ingestion** subscribes to streams of FeatureRow and writes the feature
-
-  values to registered Stores. 
-
-* **Feast Serving** handles requests for features values retrieval from the end users.
-
-**Pre-requisites**
-
-* Java SDK version 8
-* Python version 3.6 \(or above\) and pip
-* Access to Postgres database \(version 11 and above\)
-* Access to [Redis](https://redis.io/topics/quickstart) instance \(tested on version 5.x\)
-* Access to [Kafka](https://kafka.apache.org/) brokers \(tested on version 2.x\)
-* [Maven ](https://maven.apache.org/install.html) version 3.6.x
-* [grpc\_cli](https://github.com/grpc/grpc/blob/master/doc/command_line_tool.md) is useful for debugging and quick testing
-* An overview of Feast specifications and protos
-
-> **Assumptions:**
->
-> 1. Postgres is running in "localhost:5432" and has a database called "postgres" which 
->
->    can be accessed with credentials user "postgres" and password "password". 
->
->    To use different database name and credentials, please update 
->
->    "$FEAST\_HOME/core/src/main/resources/application.yml" 
->
->    or set these environment variables: DB\_HOST, DB\_USERNAME, DB\_PASSWORD.
->
-> 2. Redis is running locally and accessible from "localhost:6379"
-> 3. Feast has admin access to BigQuery.
-
-```text
-# $FEAST_HOME will refer to be the root directory of this Feast Git repository
-
-git clone https://github.com/gojek/feast
-cd feast
-```
-
-#### Starting Feast Core
-
-```text
-# Please check the default configuration for Feast Core in 
-# "$FEAST_HOME/core/src/main/resources/application.yml" and update it accordingly.
-# 
-# Start Feast Core GRPC server on localhost:6565
-mvn --projects core spring-boot:run
-
-# If Feast Core starts successfully, verify the correct Stores are registered
-# correctly, for example by using grpc_cli.
-grpc_cli call localhost:6565 GetStores ''
-
-# Should return something similar to the following.
-# Note that you should change BigQuery projectId and datasetId accordingly
-# in "$FEAST_HOME/core/src/main/resources/application.yml"
-
-store {
-  name: "SERVING"
-  type: REDIS
-  subscriptions {
-    project: "*"
-    name: "*"
-    version: "*"
-  }
-  redis_config {
-    host: "localhost"
-    port: 6379
-  }
-}
-store {
-  name: "WAREHOUSE"
-  type: BIGQUERY
-  subscriptions {
-    project: "*"
-    name: "*"
-    version: "*"
-  }
-  bigquery_config {
-    project_id: "my-google-project-id"
-    dataset_id: "my-bigquery-dataset-id"
-  }
-}
-```
-
-#### Starting Feast Serving
-
-Feast Serving requires administrators to provide an **existing** store name in Feast. An instance of Feast Serving can only retrieve features from a **single** store.
-
-> In order to retrieve features from multiple stores you must start **multiple** instances of Feast serving. If you start multiple Feast serving on a single host, make sure that they are listening on different ports.
-
-```text
-# Start Feast Serving GRPC server on localhost:6566 with store name "SERVING"
-mvn --projects serving spring-boot:run -Dspring-boot.run.arguments='--feast.store-name=SERVING'
-
-# To verify Feast Serving starts successfully
-grpc_cli call localhost:6566 GetFeastServingType ''
-
-# Should return something similar to the following.
-type: FEAST_SERVING_TYPE_ONLINE
-```
-
-#### Registering a FeatureSet
-
-Create a new FeatureSet on Feast by sending a request to Feast Core. When a feature set is successfully registered, Feast Core will start an **ingestion** job that listens for new features in the FeatureSet. Note that Feast currently only supports source of type "KAFKA", so you must have access to a running Kafka broker to register a FeatureSet successfully.
-
-```text
-# Example of registering a new driver feature set 
-# Note the source value, it assumes that you have access to a Kafka broker
-# running on localhost:9092
-
-grpc_cli call localhost:6565 ApplyFeatureSet '
-feature_set {
-  name: "driver"
-  version: 1
-
-  entities {
-    name: "driver_id"
-    value_type: INT64
-  }
-
-  features {
-    name: "city"
-    value_type: STRING
-  }
-
-  source {
-    type: KAFKA
-    kafka_source_config {
-      bootstrap_servers: "localhost:9092"
-    }
-  }
-}
-'
-
-# To check that the FeatureSet has been registered correctly.
-# You should also see logs from Feast Core of the ingestion job being started
-grpc_cli call localhost:6565 GetFeatureSets ''
-```
-
-#### Ingestion and Population of Feature Values
-
-```text
-# Produce FeatureRow messages to Kafka so it will be ingested by Feast
-# and written to the registered stores.
-# Make sure the value here is the topic assigned to the feature set
-# ... producer.send("feast-driver-features" ...)
-# 
-# Install Python SDK to help writing FeatureRow messages to Kafka
-cd $FEAST_HOME/sdk/python
-pip3 install -e .
-pip3 install pendulum
-
-# Produce FeatureRow messages to Kafka so it will be ingested by Feast
-# and written to the corresponding store.
-# Make sure the value here is the topic assigned to the feature set
-# ... producer.send("feast-test_feature_set-features" ...)
-python3 - < Tool Windows > Maven`
-2. Drill down to e.g. `Feast Core > Plugins > spring-boot:run`, right-click and `Create 'feast-core [spring-boot'…`
-3. In the dialog that pops up, check the `Resolve Workspace artifacts` box
-4. Click `OK`. You should now be able to select this run configuration for the Play button in the main toolbar, keyboard shortcuts, etc.
-
-#### Tips for Running Postgres, Redis and Kafka with Docker
-
-This guide assumes you are running Docker service on a bridge network \(which is usually the case if you're running Linux\). Otherwise, you may need to use different network options than shown below.
-
-> `--net host` usually only works as expected when you're running Docker service in bridge networking mode.
-
-```text
-# Start Postgres
-docker run --name postgres --rm -it -d --net host -e POSTGRES_DB=postgres -e POSTGRES_USER=postgres \
--e POSTGRES_PASSWORD=password postgres:12-alpine
-
-# Start Redis
-docker run --name redis --rm -it --net host -d redis:5-alpine
-
-# Start Zookeeper (needed by Kafka)
-docker run --rm \
-  --net=host \
-  --name=zookeeper \
-  --env=ZOOKEEPER_CLIENT_PORT=2181 \
-  --detach confluentinc/cp-zookeeper:5.2.1
-
-# Start Kafka
-docker run --rm \
-  --net=host \
-  --name=kafka \
-  --env=KAFKA_ZOOKEEPER_CONNECT=localhost:2181 \
-  --env=KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 \
-  --env=KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 \
-  --detach confluentinc/cp-kafka:5.2.1
-```
-
-## Code reviews
-
-Code submission to Feast \(including submission from project maintainers\) requires review and approval. Please submit a **pull request** to initiate the code review process. We use [prow](https://github.com/kubernetes/test-infra/tree/master/prow) to manage the testing and reviewing of pull requests. Please refer to [config.yaml](../.prow/config.yaml) for details on the test jobs.
-
-## Code conventions
-
-### Java
-
-We conform to the [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html). Maven can helpfully take care of that for you before you commit:
-
-```text
-$ mvn spotless:apply
-```
-
-Formatting will be checked automatically during the `verify` phase. This can be skipped temporarily:
-
-```text
-$ mvn spotless:check  # Check is automatic upon `mvn verify`
-$ mvn verify -Dspotless.check.skip
-```
-
-If you're using IntelliJ, you can import [these code style settings](https://github.com/google/styleguide/blob/gh-pages/intellij-java-google-style.xml) if you'd like to use the IDE's reformat function as you work.
-
-### Go
-
-Make sure you apply `go fmt`.
-
-## Release process
-
-Feast uses [semantic versioning](https://semver.org/).
-
-* Major and minor releases are cut from the `master` branch.
-* Whenever a major or minor release is cut, a branch is created for that release. This is called a "release branch". For example if `0.3` is released from `master`, a branch named `v0.3-branch` is created.
-* You can create a release branch via the GitHub UI.
-* From this branch a git tag is created for the specific release, for example `v0.3.0`.
-* Tagging a release will automatically build and push the relevant artifacts to their repositories or package managers \(docker images, Python wheels, etc\).
-* A release branch should be substantially _feature complete_ with respect to the intended release. Code that is committed to `master` may be merged or cherry-picked on to a release branch, but code that is directly committed to the release branch should be solely applicable to that release \(and should not be committed back to master\).
-* In general, unless you're committing code that only applies to the release stream \(for example, temporary hotfixes, backported security fixes, or image hashes\), you should commit to `master` and then merge or cherry-pick to the release branch.
-* It is also important to update the [CHANGELOG.md](https://github.com/gojek/feast/blob/master/CHANGELOG.md) when submitting a new release. This can be in the same PR or a separate PR.
-* Finally it is also important to create a [GitHub release](https://github.com/gojek/feast/releases) which includes a summary of important changes as well as any artifacts associated with that release.
-
diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md
new file mode 100644
index 00000000000..805721e3d22
--- /dev/null
+++ b/docs/contributing/contributing.md
@@ -0,0 +1,527 @@
+# Contribution Process
+
+## 1. Contribution process
+
+We use [RFCs](https://en.wikipedia.org/wiki/Request_for_Comments) and [GitHub issues](https://github.com/feast-dev/feast/issues) to communicate development ideas. The simplest way to contribute to Feast is to leave comments in our [RFCs](https://drive.google.com/drive/u/0/folders/1Lj1nIeRB868oZvKTPLYqAvKQ4O0BksjY) in the [Feast Google Drive](https://drive.google.com/drive/u/0/folders/0AAe8j7ZK3sxSUk9PVA) or our GitHub issues.
+
+Please communicate your ideas through a GitHub issue or through our Slack Channel before starting development.
+
+Please [submit a PR ](https://github.com/feast-dev/feast/pulls)to the master branch of the Feast repository once you are ready to submit your contribution. Code submission to Feast \(including submission from project maintainers\) require review and approval from maintainers or code owners. 
+
+PRs that are submitted by the general public need to be identified as `ok-to-test`. Once enabled, [Prow](https://github.com/kubernetes/test-infra/tree/master/prow) will run a range of tests to verify the submission, after which community members will help to review the pull request.
+
+{% hint style="success" %}
+Please sign the [Google CLA](https://cla.developers.google.com/) in order to have your code merged into the Feast repository.
+{% endhint %}
+
+## 2. Development guide
+
+### 2.1 Overview
+
+The following guide will help you quickly run Feast in your local machine.
+
+The main components of Feast are:
+
+* **Feast Core:** Handles feature registration, starts and manages ingestion jobs and ensures that Feast internal metadata is consistent.
+* **Feast Ingestion Jobs:** Subscribes to streams of FeatureRows and writes these as feature
+
+  values to registered databases \(online, historical\) that can be read by Feast Serving.
+
+* **Feast Serving:** Service that handles requests for features values, either online or batch.
+
+### 2.**2 Requirements**
+
+#### 2.**2.1 Development environment** 
+
+The following software is required for Feast development
+
+* Java SE Development Kit 11
+* Python version 3.6 \(or above\) and pip
+* [Maven ](https://maven.apache.org/install.html)version 3.6.x
+
+Additionally, [grpc\_cli](https://github.com/grpc/grpc/blob/master/doc/command_line_tool.md) is useful for debugging and quick testing of gRPC endpoints.
+
+#### 2.**2.2 Services**
+
+The following components/services are required to develop Feast:
+
+* **Feast Core:** Requires PostgreSQL \(version 11 and above\) to store state, and requires a Kafka \(tested on version 2.x\) setup to allow for ingestion of FeatureRows.
+* **Feast Serving:** Requires Redis \(tested on version 5.x\).
+
+These services should be running before starting development. The following snippet will start the services using Docker.
+
+```bash
+# Start Postgres
+docker run --name postgres --rm -it -d --net host -e POSTGRES_DB=postgres -e POSTGRES_USER=postgres \
+-e POSTGRES_PASSWORD=password postgres:12-alpine
+
+# Start Redis
+docker run --name redis --rm -it --net host -d redis:5-alpine
+
+# Start Zookeeper (needed by Kafka)
+docker run --rm \
+  --net=host \
+  --name=zookeeper \
+  --env=ZOOKEEPER_CLIENT_PORT=2181 \
+  --detach confluentinc/cp-zookeeper:5.2.1
+
+# Start Kafka
+docker run --rm \
+  --net=host \
+  --name=kafka \
+  --env=KAFKA_ZOOKEEPER_CONNECT=localhost:2181 \
+  --env=KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 \
+  --env=KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 \
+  --detach confluentinc/cp-kafka:5.2.1
+```
+
+### 2.3 Testing and development
+
+#### 2.3.1 Running unit tests
+
+```text
+$ mvn test
+```
+
+#### 2.3.2 Running integration tests
+
+_Note: integration suite isn't yet separated from unit._
+
+```text
+$ mvn verify
+```
+
+#### 2.3.3 Running components locally
+
+The `core` and `serving` modules are Spring Boot applications. These may be run as usual for [the Spring Boot Maven plugin](https://docs.spring.io/spring-boot/docs/current/maven-plugin/index.html):
+
+```text
+$ mvn --projects core spring-boot:run
+
+# Or for short:
+$ mvn -pl core spring-boot:run
+```
+
+Note that you should execute `mvn` from the Feast repository root directory, as there are intermodule dependencies that Maven will not resolve if you `cd` to subdirectories to run.
+
+#### 2.3.4 Running from IntelliJ
+
+Compiling and running tests in IntelliJ should work as usual.
+
+Running the Spring Boot apps may work out of the box in IDEA Ultimate, which has built-in support for Spring Boot projects, but the Community Edition needs a bit of help:
+
+The Spring Boot Maven plugin automatically puts dependencies with `provided` scope on the runtime classpath when using `spring-boot:run`, such as its embedded Tomcat server. The "Play" buttons in the gutter or right-click menu of a `main()` method [do not do this](https://stackoverflow.com/questions/30237768/run-spring-boots-main-using-ide).
+
+A solution to this is:
+
+1. Open `View > Tool Windows > Maven`
+2. Drill down to e.g. `Feast Core > Plugins > spring-boot:run`, right-click and `Create 'feast-core [spring-boot'…`
+3. In the dialog that pops up, check the `Resolve Workspace artifacts` box
+4. Recommended: add `-Dspring-boot.run.fork=false` to the `Command line` field to get Debug working too
+5. Click `OK`. You should now be able to select this run configuration for the Play button in the main toolbar, keyboard shortcuts, etc.
+
+It is recommend to have IntelliJ delegate building to Maven, if this is not enabled out of the box when you import the project, for greater assurance that build behavior is consistent with CI / production builds. This is set in Preferences at `Build, Execution, Deployment > Build Tools > Maven > Runner > Delegate IDE build/run actions to Maven`.
+
+### 2.**4** Validating your setup
+
+The following section is a quick walk-through to test whether your local Feast deployment is functional for development purposes.
+
+**2.4.1 Assumptions**
+
+* PostgreSQL is running in `localhost:5432` and has a database called `postgres` which
+
+  can be accessed with credentials user `postgres` and password `password`. Different database configurations can be supplied here \(`/core/src/main/resources/application.yml`\)
+
+* Redis is running locally and accessible from `localhost:6379`   
+* \(optional\) The local environment has been authentication with Google Cloud Platform and has full access to BigQuery. This is only necessary for BigQuery testing/development.
+
+#### 2.4.2 Clone Feast
+
+```bash
+git clone https://github.com/feast-dev/feast.git && cd feast && \
+export FEAST_HOME_DIR=$(pwd)
+```
+
+#### 2.4.3 Starting Feast Core
+
+To run Feast Core locally using Maven:
+
+```bash
+# Feast Core can be configured from the following .yml file
+# $FEAST_HOME_DIR/core/src/main/resources/application.yml
+mvn --projects core spring-boot:run
+```
+
+Test whether Feast Core is running
+
+```text
+grpc_cli call localhost:6565 ListStores ''
+```
+
+The output should list **no** stores since no Feast Serving has registered its stores to Feast Core:
+
+```text
+connecting to localhost:6565
+
+Rpc succeeded with OK status
+```
+
+#### 2.4.4 Starting Feast Serving
+
+Feast Serving is configured through the `$FEAST_HOME_DIR/serving/src/main/resources/application.yml`. Each Serving deployment must be configured with a store. The default store is Redis \(used for online serving\).
+
+The configuration for this default store is located in a separate `.yml` file. The default location is `$FEAST_HOME_DIR/serving/sample_redis_config.yml`:   
+
+```text
+name: serving
+type: REDIS
+redis_config:
+  host: localhost
+  port: 6379
+subscriptions:
+  - name: "*"
+    project: "*"
+    version: "*"
+```
+
+Once Feast Serving is started, it will register its store with Feast Core \(by name\) and start to subscribe to a feature sets based on its subscription. 
+
+Start Feast Serving GRPC server on localhost:6566 with store name `serving`
+
+```text
+mvn --projects serving spring-boot:run
+```
+
+Test connectivity to Feast Serving
+
+```text
+grpc_cli call localhost:6566 GetFeastServingInfo ''
+```
+
+```text
+connecting to localhost:6566
+version: "0.4.2-SNAPSHOT"
+type: FEAST_SERVING_TYPE_ONLINE
+
+Rpc succeeded with OK status
+```
+
+Test Feast Core to see whether it is aware of the Feast Serving deployment
+
+```text
+grpc_cli call localhost:6565 ListStores ''
+```
+
+```text
+connecting to localhost:6565
+store {
+  name: "serving"
+  type: REDIS
+  subscriptions {
+    name: "*"
+    version: "*"
+    project: "*"
+  }
+  redis_config {
+    host: "localhost"
+    port: 6379
+  }
+}
+
+Rpc succeeded with OK status
+```
+
+In order to use BigQuery as a historical store, it is necessary to start Feast Serving with a different store type. 
+
+Copy `$FEAST_HOME_DIR/serving/sample_redis_config.yml`  to the following location `$FEAST_HOME_DIR/serving/my_bigquery_config.yml` and update the configuration as below:
+
+```text
+name: bigquery
+type: BIGQUERY
+bigquery_config:
+  project_id: YOUR_GCP_PROJECT_ID
+  dataset_id: YOUR_GCP_DATASET
+subscriptions:
+  - name: "*"
+    version: "*"
+    project: "*"
+```
+
+Then inside `serving/src/main/resources/application.yml` modify the following key `feast.store.config-path`  to point to the new store configuration.
+
+After making these changes, restart Feast Serving:
+
+```text
+mvn --projects serving spring-boot:run
+```
+
+You should see two stores registered:
+
+```text
+store {
+  name: "serving"
+  type: REDIS
+  subscriptions {
+    name: "*"
+    version: "*"
+    project: "*"
+  }
+  redis_config {
+    host: "localhost"
+    port: 6379
+  }
+}
+store {
+  name: "bigquery"
+  type: BIGQUERY
+  subscriptions {
+    name: "*"
+    version: "*"
+    project: "*"
+  }
+  bigquery_config {
+    project_id: "my_project"
+    dataset_id: "my_bq_dataset"
+  }
+}
+```
+
+#### 2.4.5 Registering a FeatureSet
+
+Before registering a new FeatureSet, a project is required.
+
+```text
+grpc_cli call localhost:6565 CreateProject '
+  name: "your_project_name"
+'
+```
+
+When a feature set is successfully registered, Feast Core will start an **ingestion** job that listens for new features in the feature set.
+
+{% hint style="info" %}
+Note that Feast currently only supports source of type `KAFKA`, so you must have access to a running Kafka broker to register a FeatureSet successfully. It is possible to omit the `source` from a Feature Set, but Feast Core will still use Kafka behind the scenes, it is simply abstracted away from the user. 
+{% endhint %}
+
+Create a new FeatureSet in Feast by sending a request to Feast Core:
+
+```text
+# Example of registering a new driver feature set
+# Note the source value, it assumes that you have access to a Kafka broker
+# running on localhost:9092
+
+grpc_cli call localhost:6565 ApplyFeatureSet '
+feature_set {
+  spec {
+    project: "your_project_name"
+    name: "driver"
+    version: 1
+
+    entities {
+      name: "driver_id"
+      value_type: INT64
+    }
+
+    features {
+      name: "city"
+      value_type: STRING
+    }
+
+    source {
+      type: KAFKA
+      kafka_source_config {
+        bootstrap_servers: "localhost:9092"
+        topic: "your-kafka-topic"
+      }
+    }
+  }
+}
+'
+```
+
+Verify that the FeatureSet has been registered correctly.
+
+```text
+# To check that the FeatureSet has been registered correctly.
+# You should also see logs from Feast Core of the ingestion job being started
+grpc_cli call localhost:6565 GetFeatureSet '
+  project: "your_project_name"
+  name: "driver"
+'
+```
+
+Or alternatively, list all feature sets
+
+```text
+grpc_cli call localhost:6565 ListFeatureSets '
+  filter {
+    project: "your_project_name"
+    feature_set_name: "driver"
+    feature_set_version: "1"
+  }
+'
+```
+
+#### 2.4.6 Ingestion and Population of Feature Values
+
+```text
+# Produce FeatureRow messages to Kafka so it will be ingested by Feast
+# and written to the registered stores.
+# Make sure the value here is the topic assigned to the feature set
+# ... producer.send("feast-driver-features" ...)
+#
+# Install Python SDK to help writing FeatureRow messages to Kafka
+cd $FEAST_HOMEDIR/sdk/python
+pip3 install -e .
+pip3 install pendulum
+
+# Produce FeatureRow messages to Kafka so it will be ingested by Feast
+# and written to the corresponding store.
+# Make sure the value here is the topic assigned to the feature set
+# ... producer.send("feast-test_feature_set-features" ...)
+python3 - < Tool Windows > Maven`
+2. Drill down to e.g. `Feast Core > Plugins > spring-boot:run`, right-click and `Create 'feast-core [spring-boot'…`
+3. In the dialog that pops up, check the `Resolve Workspace artifacts` box
+4. Click `OK`. You should now be able to select this run configuration for the Play button in the main toolbar, keyboard shortcuts, etc.
+
+## **4** Validating your setup
+
+The following section is a quick walk-through to test whether your local Feast deployment is functional for development purposes.
+
+**4.1 Assumptions**
+
+* PostgreSQL is running in `localhost:5432` and has a database called `postgres` which
+
+  can be accessed with credentials user `postgres` and password `password`. Different database configurations can be supplied here \(`/core/src/main/resources/application.yml`\)
+
+* Redis is running locally and accessible from `localhost:6379`
+* \(optional\) The local environment has been authentication with Google Cloud Platform and has full access to BigQuery. This is only necessary for BigQuery testing/development.
+
+### 4.2 Clone Feast
+
+```bash
+git clone https://github.com/feast-dev/feast.git && cd feast && \
+export FEAST_HOME_DIR=$(pwd)
+```
+
+### 4.3 Starting Feast Core
+
+To run Feast Core locally using Maven:
+
+```bash
+# Feast Core can be configured from the following .yml file
+# $FEAST_HOME_DIR/core/src/main/resources/application.yml
+mvn --projects core spring-boot:run
+```
+
+Test whether Feast Core is running
+
+```text
+grpc_cli call localhost:6565 ListStores ''
+```
+
+The output should list **no** stores since no Feast Serving has registered its stores to Feast Core:
+
+```text
+connecting to localhost:6565
+
+Rpc succeeded with OK status
+```
+
+### 4.4 Starting Feast Serving
+
+Feast Serving is configured through the `$FEAST_HOME_DIR/serving/src/main/resources/application.yml`. Each Serving deployment must be configured with a store. The default store is Redis \(used for online serving\).
+
+The configuration for this default store is located in a separate `.yml` file. The default location is `$FEAST_HOME_DIR/serving/sample_redis_config.yml`:
+
+```text
+name: serving
+type: REDIS
+redis_config:
+  host: localhost
+  port: 6379
+subscriptions:
+  - name: "*"
+    project: "*"
+    version: "*"
+```
+
+Once Feast Serving is started, it will register its store with Feast Core \(by name\) and start to subscribe to a feature sets based on its subscription.
+
+Start Feast Serving GRPC server on localhost:6566 with store name `serving`
+
+```text
+mvn --projects serving spring-boot:run
+```
+
+Test connectivity to Feast Serving
+
+```text
+grpc_cli call localhost:6566 GetFeastServingInfo ''
+```
+
+```text
+connecting to localhost:6566
+version: "0.4.2-SNAPSHOT"
+type: FEAST_SERVING_TYPE_ONLINE
+
+Rpc succeeded with OK status
+```
+
+Test Feast Core to see whether it is aware of the Feast Serving deployment
+
+```text
+grpc_cli call localhost:6565 ListStores ''
+```
+
+```text
+connecting to localhost:6565
+store {
+  name: "serving"
+  type: REDIS
+  subscriptions {
+    name: "*"
+    version: "*"
+    project: "*"
+  }
+  redis_config {
+    host: "localhost"
+    port: 6379
+  }
+}
+
+Rpc succeeded with OK status
+```
+
+In order to use BigQuery as a historical store, it is necessary to start Feast Serving with a different store type.
+
+Copy `$FEAST_HOME_DIR/serving/sample_redis_config.yml` to the following location `$FEAST_HOME_DIR/serving/my_bigquery_config.yml` and update the configuration as below:
+
+```text
+name: bigquery
+type: BIGQUERY
+bigquery_config:
+  project_id: YOUR_GCP_PROJECT_ID
+  dataset_id: YOUR_GCP_DATASET
+subscriptions:
+  - name: "*"
+    version: "*"
+    project: "*"
+```
+
+Then inside `serving/src/main/resources/application.yml` modify the following key `feast.store.config-path` to point to the new store configuration.
+
+After making these changes, restart Feast Serving:
+
+```text
+mvn --projects serving spring-boot:run
+```
+
+You should see two stores registered:
+
+```text
+store {
+  name: "serving"
+  type: REDIS
+  subscriptions {
+    name: "*"
+    version: "*"
+    project: "*"
+  }
+  redis_config {
+    host: "localhost"
+    port: 6379
+  }
+}
+store {
+  name: "bigquery"
+  type: BIGQUERY
+  subscriptions {
+    name: "*"
+    version: "*"
+    project: "*"
+  }
+  bigquery_config {
+    project_id: "my_project"
+    dataset_id: "my_bq_dataset"
+  }
+}
+```
+
+### 4.5 Registering a FeatureSet
+
+Before registering a new FeatureSet, a project is required.
+
+```text
+grpc_cli call localhost:6565 CreateProject '
+  name: "your_project_name"
+'
+```
+
+When a feature set is successfully registered, Feast Core will start an **ingestion** job that listens for new features in the feature set.
+
+{% hint style="info" %}
+Note that Feast currently only supports source of type `KAFKA`, so you must have access to a running Kafka broker to register a FeatureSet successfully. It is possible to omit the `source` from a Feature Set, but Feast Core will still use Kafka behind the scenes, it is simply abstracted away from the user.
+{% endhint %}
+
+Create a new FeatureSet in Feast by sending a request to Feast Core:
+
+```text
+# Example of registering a new driver feature set
+# Note the source value, it assumes that you have access to a Kafka broker
+# running on localhost:9092
+
+grpc_cli call localhost:6565 ApplyFeatureSet '
+feature_set {
+  spec {
+    project: "your_project_name"
+    name: "driver"
+    version: 1
+
+    entities {
+      name: "driver_id"
+      value_type: INT64
+    }
+
+    features {
+      name: "city"
+      value_type: STRING
+    }
+
+    source {
+      type: KAFKA
+      kafka_source_config {
+        bootstrap_servers: "localhost:9092"
+        topic: "your-kafka-topic"
+      }
+    }
+  }
+}
+'
+```
+
+Verify that the FeatureSet has been registered correctly.
+
+```text
+# To check that the FeatureSet has been registered correctly.
+# You should also see logs from Feast Core of the ingestion job being started
+grpc_cli call localhost:6565 GetFeatureSet '
+  project: "your_project_name"
+  name: "driver"
+'
+```
+
+Or alternatively, list all feature sets
+
+```text
+grpc_cli call localhost:6565 ListFeatureSets '
+  filter {
+    project: "your_project_name"
+    feature_set_name: "driver"
+    feature_set_version: "1"
+  }
+'
+```
+
+### 4.6 Ingestion and Population of Feature Values
+
+```text
+# Produce FeatureRow messages to Kafka so it will be ingested by Feast
+# and written to the registered stores.
+# Make sure the value here is the topic assigned to the feature set
+# ... producer.send("feast-driver-features" ...)
+#
+# Install Python SDK to help writing FeatureRow messages to Kafka
+cd $FEAST_HOMEDIR/sdk/python
+pip3 install -e .
+pip3 install pendulum
+
+# Produce FeatureRow messages to Kafka so it will be ingested by Feast
+# and written to the corresponding store.
+# Make sure the value here is the topic assigned to the feature set
+# ... producer.send("feast-test_feature_set-features" ...)
+python3 - <` set for `maven-compiler-plugin` in the module's `pom.xml` \(11 is project default if the module doesn't set one\).
+
+Ensure that the Project _SDK_ \(not language level\) is set to a Java 11 JDK, and that all modules use the Project SDK \(see [the Dependencies tab](https://www.jetbrains.com/help/idea/dependencies.html) in the Modules view\).
+
diff --git a/docs/contributing/release-process.md b/docs/contributing/release-process.md
new file mode 100644
index 00000000000..72f91203c0d
--- /dev/null
+++ b/docs/contributing/release-process.md
@@ -0,0 +1,62 @@
+# Releasing Feast
+
+## Versioning policy and branch workflow
+
+Feast uses [semantic versioning](https://semver.org/). As such, while it is still pre-1.0 breaking changes will happen in minor versions.
+
+Contributors are encouraged to understand our branch workflow described below, for choosing where to branch when making a change (and thus the merge base for a pull request).
+
+* Major and minor releases are cut from the `master` branch.
+* Each major and minor release has a long-lived maintenance branch, for example `v0.3-branch`. This is called a "release branch".
+* From the release branches, patch version releases are tagged, for example `v0.3.0`.
+
+A release branch should be substantially _feature complete_ with respect to the intended release. Code that is committed to `master` may be merged or cherry-picked on to a release branch, but code that is directly committed to a release branch should be solely applicable to that release \(and should not be committed back to master\).
+
+In general, unless you're committing code that only applies to a particular release stream \(for example, temporary hotfixes, backported security fixes, or image hashes\), you should base changes from `master` and then merge or cherry-pick to the release branch.
+
+## Release process
+
+For Feast maintainers, these are the concrete steps for making a new release.
+
+1. For a major or minor release, create and check out the release branch for the new stream, e.g. `v0.6-branch`. For a patch version, check out the stream's release branch.
+1. Update the [CHANGELOG.md]. See the [Creating a change log](#creating-a-change-log) guide.
+1. In the root `pom.xml`, remove `-SNAPSHOT` from the `` property, and commit.
+1. Push. For a new release branch, open a PR against master.
+1. When CI passes, merge. (Remember _not_ to delete the new release branch).
+1. Tag the merge commit with the release version, using a `v` prefix. Push the tag.
+1. Bump to the next working version and append `-SNAPSHOT` in `pom.xml`.
+1. Commit the POM and open a PR.
+1. Create a [GitHub release](https://github.com/feast-dev/feast/releases) which includes a summary of important changes as well as any artifacts associated with the release. Make sure to include the same change log as added in [CHANGELOG.md]. Use `Feast vX.Y.Z` as the title.
+1. Create one final PR to the master branch and also update its [CHANGELOG.md].
+
+When a tag that matches a Semantic Version string is pushed, CI will automatically build and push the relevant artifacts to their repositories or package managers \(docker images, Python wheels, etc\). JVM artifacts are promoted from Sonatype OSSRH to Maven Central, but it sometimes takes some time for them to be available.
+
+[CHANGELOG.md]: https://github.com/feast-dev/feast/blob/master/CHANGELOG.md
+
+### Creating a change log
+
+We use an [open source change log generator](https://hub.docker.com/r/ferrarimarco/github-changelog-generator/) to generate change logs. The process still requires a little bit of manual effort.
+1. Create a GitHub token as [per these instructions ](https://github.com/github-changelog-generator/github-changelog-generator#github-token). The token is used as an input argument (`-t`) to the changelog generator.
+2. The change log generator configuration below will look for unreleased changes on a specific branch. The branch will be `master` for a major/minor release, or a release branch (`v0.4-branch`) for a patch release. You will need to set the branch using the `--release-branch` argument.
+3. You should also set the `--future-release` argument. This is the version you are releasing. The version can still be changed at a later date.
+4. Update the  arguments below and run the command to generate the change log to the console.
+```
+docker run -it --rm ferrarimarco/github-changelog-generator \
+--user feast-dev \
+--project feast  \
+--release-branch   \
+--future-release   \
+--unreleased-only  \
+--no-issues  \
+--bug-labels kind/bug  \
+--enhancement-labels kind/feature  \
+--breaking-labels compat/breaking  \
+-t   \
+--max-issues 1 \
+-o
+```
+5. Review each change log item.
+    - Make sure that sentences are grammatically correct and well formatted (although we will try to enforce this at the PR review stage). 
+    - Make sure that each item is categorized correctly. You will see the following categories: `Breaking changes`, `Implemented enhancements`, `Fixed bugs`, and `Merged pull requests`. Any unlabeled PRs will be found in `Merged pull requests`. It's important to make sure that any `breaking changes`, `enhancements`, or `bug fixes` are pulled up out of `merged pull requests` into the correct category. Housekeeping, tech debt clearing, infra changes, or refactoring do not count as `enhancements`. Only enhancements a user benefits from should be listed in that category.
+    - Make sure that the "Full Changelog" link is actually comparing the correct tags (normally your released version against the previously version).
+    - Make sure that release notes and breaking changes are present.
diff --git a/docs/contributing/style-guide.md b/docs/contributing/style-guide.md
new file mode 100644
index 00000000000..322f1e3d034
--- /dev/null
+++ b/docs/contributing/style-guide.md
@@ -0,0 +1,27 @@
+# Style Guide
+
+## 1. Java
+
+We conform to the [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html). Maven can helpfully take care of that for you before you commit:
+
+```text
+$ mvn spotless:apply
+```
+
+Formatting will be checked automatically during the `verify` phase. This can be skipped temporarily:
+
+```text
+$ mvn spotless:check  # Check is automatic upon `mvn verify`
+$ mvn verify -Dspotless.check.skip
+```
+
+If you're using IntelliJ, you can import [these code style settings](https://github.com/google/styleguide/blob/gh-pages/intellij-java-google-style.xml) if you'd like to use the IDE's reformat function as you develop.
+
+## 2. Go
+
+Make sure you apply `go fmt`.
+
+## 3. Python
+
+We use [Python Black](https://github.com/psf/black) to format our Python code prior to submission.
+
diff --git a/docs/coverage/java/pom.xml b/docs/coverage/java/pom.xml
new file mode 100644
index 00000000000..05ffaf374e0
--- /dev/null
+++ b/docs/coverage/java/pom.xml
@@ -0,0 +1,111 @@
+
+
+
+  4.0.0
+
+  
+
+  
+    dev.feast
+    feast-parent
+    ${revision}
+    ../../..
+  
+
+  Feast Coverage Java
+  feast-coverage
+
+  
+    true
+  
+
+  
+    
+      dev.feast
+      feast-storage-api
+      ${project.version}
+    
+
+    
+      dev.feast
+      feast-storage-connector-bigquery
+      ${project.version}
+    
+
+    
+      dev.feast
+      feast-storage-connector-redis
+      ${project.version}
+    
+
+    
+      dev.feast
+      feast-storage-connector-redis-cluster
+      ${project.version}
+    
+
+    
+      dev.feast
+      feast-ingestion
+      ${project.version}
+    
+
+    
+      dev.feast
+      feast-core
+      ${project.version}
+    
+
+    
+      dev.feast
+      feast-serving
+      ${project.version}
+    
+
+    
+      dev.feast
+      feast-sdk
+      ${project.version}
+    
+  
+
+  
+    
+      
+        org.jacoco
+        jacoco-maven-plugin
+        
+          
+            report-aggregate
+            prepare-package
+            
+              report-aggregate
+            
+          
+        
+      
+    
+  
+
+
diff --git a/docs/docs/.gitbook/assets/feast-docs-overview-diagram-2.svg b/docs/docs/.gitbook/assets/feast-docs-overview-diagram-2.svg
new file mode 100644
index 00000000000..7f30963ec78
--- /dev/null
+++ b/docs/docs/.gitbook/assets/feast-docs-overview-diagram-2.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/getting-help.md b/docs/getting-help.md
deleted file mode 100644
index d8180ab7842..00000000000
--- a/docs/getting-help.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# Getting Help
-
-## Chat
-
-* Come and chat with us in the [\#Feast Slack channel in the Kubeflow workspace](https://join.slack.com/t/kubeflow/shared_invite/enQtNDg5MTM4NTQyNjczLTdkNTVhMjg1ZTExOWI0N2QyYTQ2MTIzNTJjMWRiOTFjOGRlZWEzODc1NzMwNTMwM2EzNjY1MTFhODczNjk4MTk) and catch up on all things Feast!
-
-## GitHub
-
-* Feast's GitHub repo can be [found here](https://github.com/gojek/feast/).
-* Found a bug or need a feature? [Create an issue on GitHub](https://github.com/gojek/feast/issues/new)
-
-## Mailing list
-
-### Feast discussion
-
-* Google Group: [https://groups.google.com/d/forum/feast-discuss](https://groups.google.com/d/forum/feast-discuss)
-* Mailing List: [feast-discuss@googlegroups.com](mailto:feast-discuss@googlegroups.com)
-
-### Feast development
-
-* Google Group: [https://groups.google.com/d/forum/feast-dev](https://groups.google.com/d/forum/feast-dev)
-* Mailing List: [feast-dev@googlegroups.com](mailto:feast-dev@googlegroups.com)
-
diff --git a/docs/getting-started/installing-feast.md b/docs/getting-started/installing-feast.md
deleted file mode 100644
index 0b8b42f26f6..00000000000
--- a/docs/getting-started/installing-feast.md
+++ /dev/null
@@ -1,424 +0,0 @@
-# Installing Feast
-
-## Overview
-
-This installation guide will demonstrate three ways of installing Feast:
-
-* \*\*\*\*[**Docker Compose \(Quickstart\):**](installing-feast.md#docker-compose) Fastest way to get Feast up and running. Provides a pre-installed Jupyter Notebook with the Feast Python SDK and sample code.
-* [**Minikube**](installing-feast.md#minikube)**:** This installation has no external dependencies, but does not have a historical feature store installed. It allows users to quickly get a feel for Feast.
-* [**Google Kubernetes Engine:**](installing-feast.md#google-kubernetes-engine) This guide installs a single cluster Feast installation on Google's GKE. It has Google Cloud specific dependencies like BigQuery, Dataflow, and Google Cloud Storage.
-
-## Docker Compose \(Quickstart\)
-
-### Overview
-
-A docker compose file is provided to quickly test Feast with the official docker images. There is no hard dependency on GCP, unless batch serving is required. Once you have set up Feast using Docker Compose, you will be able to:
-
-* Create, register, and manage feature sets
-* Ingest feature data into Feast
-* Retrieve features for online serving
-
-{% hint style="info" %}
-The docker compose setup uses Direct Runner for the Apache Beam jobs. Running Beam with the Direct Runner means it does not need a dedicated runner like Flink or Dataflow, but this comes at the cost of performance. We recommend the use of a full runner when running Feast with very large workloads.
-{% endhint %}
-
-### 0. Requirements
-
-* [Docker compose](https://docs.docker.com/compose/install/) should be installed.
-* TCP ports 6565, 6566, 8888, and 9094 should not be in use. Otherwise, modify the port mappings in  `infra/docker-compose/docker-compose.yml` to use unoccupied ports.
-* \(for batch serving only\) For batch serving you will also need a [GCP service account key](https://cloud.google.com/iam/docs/creating-managing-service-account-keys) that has access to GCS and BigQuery. Port 6567 will be used for the batch serving endpoint.
-* \(for batch serving only\) [Google Cloud SDK ](https://cloud.google.com/sdk/install)installed, authenticated, and configured to the project you will use.
-
-### 1. Step-by-step guide \(Online serving only\)
-
-Clone the [Feast repository](https://github.com/gojek/feast/) and navigate to the `docker-compose` sub-directory:
-
-```bash
-git clone https://github.com/gojek/feast.git && \
-cd feast && export FEAST_HOME_DIR=$(pwd) && \
-cd infra/docker-compose
-```
-
-Make a copy of the `.env.sample` file:
-
-```bash
-cp .env.sample .env
-```
-
-Start Feast:
-
-```javascript
-docker-compose up -d
-```
-
-A Jupyter notebook is now available to use Feast:
-
-[http://localhost:8888/notebooks/feast-notebooks/feast-quickstart.ipynb](http://localhost:8888/notebooks/feast-notebooks/feast-quickstart.ipynb)
-
-### 2. Step-by-step guide \(Batch and online serving\)
-
-Clone the [Feast repository](https://github.com/gojek/feast/) and navigate to the `docker-compose` sub-directory:
-
-```bash
-git clone https://github.com/gojek/feast.git && \
-cd feast && export FEAST_HOME_DIR=$(pwd) && \
-cd infra/docker-compose
-```
-
-Create a [service account ](https://cloud.google.com/iam/docs/creating-managing-service-accounts)from the GCP console and copy it to the `gcp-service-accounts` folder:
-
-```javascript
-cp my-service-account.json ${FEAST_HOME_DIR}/infra/docker-compose/gcp-service-accounts
-```
-
-Create a Google Cloud Storage bucket. Make sure that your service account above has read/write permissions to this bucket:
-
-```bash
-gsutil mb gs://my-feast-staging-bucket
-```
-
-Make a copy of the `.env.sample` file:
-
-```bash
-cp .env.sample .env
-```
-
-Customize the `.env` file based on your environment. At the very least you have to modify:
-
-* **FEAST\_CORE\_GCP\_SERVICE\_ACCOUNT\_KEY:** This should be your service account file name without the .json extension.
-* **FEAST\_BATCH\_SERVING\_GCP\_SERVICE\_ACCOUNT\_KEY:** This should be your service account file name without the .json extension.
-* **FEAST\_JUPYTER\_GCP\_SERVICE\_ACCOUNT\_KEY:** This should be your service account file name without the .json extension.
-* **FEAST\_JOB\_STAGING\_LOCATION:** Google Cloud Storage bucket that Feast will use to stage data exports and batch retrieval requests.
-
-We will also need to customize the `bq-store.yml` file inside `infra/docker-compose/serving/` to configure the BigQuery storage configuration as well as the feature sets that the store subscribes to. At a minimum you will need to set:
-
-* **project\_id:** This is you GCP project id.
-* **dataset\_id:** This is the name of the BigQuery dataset that tables will be created in. Each feature set will have one table in BigQuery.
-
-Start Feast:
-
-```javascript
-docker-compose -f docker-compose.yml -f docker-compose.batch.yml up -d
-```
-
-A Jupyter notebook is now available to use Feast:
-
-[http://localhost:8888/notebooks/feast-notebooks](http://localhost:8888/tree/feast-notebooks)
-
-## Minikube
-
-### Overview
-
-This guide will install Feast into [Minikube](https://github.com/kubernetes/minikube). Once Feast is installed you will be able to:
-
-* Define and register features.
-* Load feature data from both batch and streaming sources.
-* Retrieve features for online serving.
-
-{% hint style="warning" %}
-This Minikube installation guide is for demonstration purposes only. It is not meant for production use, and does not install a historical feature store.
-{% endhint %}
-
-### 0. Requirements
-
-The following software should be installed prior to starting:
-
-1. [Minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/) should be installed.
-2. [Kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) installed and configured to work with Minikube.
-3. [Helm](https://helm.sh/3) \(2.16.0 or greater\). Helm 3 has not been tested yet.
-
-### 1. Set up Minikube
-
-Start Minikube. Note the minimum cpu and memory below:
-
-```bash
-minikube start --cpus=3 --memory=4096 --kubernetes-version='v1.15.5'
-```
-
-Set up your Feast environmental variables
-
-```bash
-export FEAST_IP=$(minikube ip)
-export FEAST_CORE_URL=${FEAST_IP}:32090
-export FEAST_SERVING_URL=${FEAST_IP}:32091
-```
-
-### 2. Install Feast with Helm
-
-Clone the [Feast repository](https://github.com/gojek/feast/) and navigate to the `charts` sub-directory:
-
-```bash
-git clone https://github.com/gojek/feast.git && \
-cd feast && export FEAST_HOME_DIR=$(pwd) && \
-cd infra/charts/feast
-```
-
-Copy the `values-demo.yaml` file for your installation:
-
-```bash
-cp values-demo.yaml my-feast-values.yaml
-```
-
-Update all occurrences of the domain `feast.example.com` inside of `my-feast-values.yaml` with your Minikube IP. This is to allow external access to the services in the cluster. You can find your Minikube IP by running the following command `minikube ip`, or simply replace the text from the command line:
-
-```bash
-sed -i "s/feast.example.com/${FEAST_IP}/g" my-feast-values.yaml
-```
-
-Install Tiller:
-
-```bash
-helm init
-```
-
-Install the Feast Helm chart:
-
-```bash
-helm install --name feast -f my-feast-values.yaml .
-```
-
-Ensure that the system comes online. This will take a few minutes
-
-```bash
-watch kubectl get pods
-```
-
-```bash
-NAME                                           READY   STATUS      RESTARTS   AGE
-pod/feast-feast-core-666fd46db4-l58l6          1/1     Running     0          5m
-pod/feast-feast-serving-online-84d99ddcbd      1/1     Running     0          6m
-pod/feast-kafka-0                              1/1     Running     0          3m
-pod/feast-kafka-1                              1/1     Running     0          4m
-pod/feast-kafka-2                              1/1     Running     0          4m
-pod/feast-postgresql-0                         1/1     Running     0          5m
-pod/feast-redis-master-0                       1/1     Running     0          5m
-pod/feast-zookeeper-0                          1/1     Running     0          5m
-pod/feast-zookeeper-1                          1/1     Running     0          5m
-pod/feast-zookeeper-2                          1/1     Running     0          5m
-```
-
-### 3. Connect to Feast with the Python SDK
-
-Install the Python SDK using pip:
-
-```bash
-pip install -e ${FEAST_HOME_DIR}/sdk/python
-```
-
-Configure the Feast Python SDK:
-
-```bash
-feast config set core_url ${FEAST_CORE_URL}
-feast config set serving_url ${FEAST_SERVING_URL}
-```
-
-That's it! You can now start to use Feast!
-
-## Google Kubernetes Engine
-
-### Overview 
-
-This guide will install Feast into a Kubernetes cluster on GCP. It assumes that all of your services will run within a single K8s cluster. Once Feast is installed you will be able to:
-
-* Define and register features.
-* Load feature data from both batch and streaming sources.
-* Retrieve features for model training.
-* Retrieve features for online serving.
-
-{% hint style="info" %}
-This guide requires [Google Cloud Platform](https://cloud.google.com/) for installation.
-
-* [BigQuery](https://cloud.google.com/bigquery/) is used for storing historical features.
-* [Cloud Dataflow](https://cloud.google.com/dataflow/) is used for running data ingestion jobs.
-* [Google Cloud Storage](https://cloud.google.com/storage/) is used for intermediate data storage.
-{% endhint %}
-
-### 0. Requirements 
-
-1. [Google Cloud SDK ](https://cloud.google.com/sdk/install)installed, authenticated, and configured to the project you will use.
-2. [Kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) installed.
-3. [Helm](https://helm.sh/3) \(2.16.0 or greater\) installed on your local machine with Tiller installed in your cluster. Helm 3 has not been tested yet.
-
-### 1. Set up GCP
-
-First define the environmental variables that we will use throughout this installation. Please customize these to reflect your environment.
-
-```bash
-export FEAST_GCP_PROJECT_ID=my-gcp-project
-export FEAST_GCP_REGION=us-central1
-export FEAST_GCP_ZONE=us-central1-a
-export FEAST_BIGQUERY_DATASET_ID=feast
-export FEAST_GCS_BUCKET=${FEAST_GCP_PROJECT_ID}_feast_bucket
-export FEAST_GKE_CLUSTER_NAME=feast
-export FEAST_S_ACCOUNT_NAME=feast-sa
-```
-
-Create a Google Cloud Storage bucket for Feast to stage data during exports:
-
-```bash
-gsutil mb gs://${FEAST_GCS_BUCKET}
-```
-
-Create a BigQuery dataset for storing historical features:
-
-```bash
-bq mk ${FEAST_BIGQUERY_DATASET_ID}
-```
-
-Create the service account that Feast will run as:
-
-```bash
-gcloud iam service-accounts create ${FEAST_SERVICE_ACCOUNT_NAME}
-
-gcloud projects add-iam-policy-binding ${FEAST_GCP_PROJECT_ID} \
-  --member serviceAccount:${FEAST_S_ACCOUNT_NAME}@${FEAST_GCP_PROJECT_ID}.iam.gserviceaccount.com \
-  --role roles/editor
-
-gcloud iam service-accounts keys create key.json --iam-account \
-${FEAST_S_ACCOUNT_NAME}@${FEAST_GCP_PROJECT_ID}.iam.gserviceaccount.com
-```
-
-Ensure that [Dataflow API is enabled](https://console.cloud.google.com/apis/api/dataflow.googleapis.com/overview):
-
-```bash
-gcloud services enable dataflow.googleapis.com
-```
-
-### 2. Set up a Kubernetes \(GKE\) cluster
-
-{% hint style="warning" %}
-Provisioning a GKE cluster can expose your services publicly. This guide does not cover securing access to the cluster.
-{% endhint %}
-
-Create a GKE cluster:
-
-```bash
-gcloud container clusters create ${FEAST_GKE_CLUSTER_NAME} \
-    --machine-type n1-standard-4
-```
-
-Create a secret in the GKE cluster based on your local key `key.json`:
-
-```bash
-kubectl create secret generic feast-gcp-service-account --from-file=key.json
-```
-
-For this guide we will use `NodePort` for exposing Feast services. In order to do so, we must find an internal IP of at least one GKE node.
-
-```bash
-export FEAST_IP=$(kubectl describe nodes | grep InternalIP | awk '{print $2}' | head -n 1)
-export FEAST_CORE_URL=${FEAST_IP}:32090
-export FEAST_ONLINE_SERVING_URL=${FEAST_IP}:32091
-export FEAST_BATCH_SERVING_URL=${FEAST_IP}:32092
-```
-
-Confirm that you are able to access this node:
-
-```bash
-ping $FEAST_IP
-```
-
-```bash
-PING 10.123.114.11 (10.203.164.22) 56(84) bytes of data.
-64 bytes from 10.123.114.11: icmp_seq=1 ttl=63 time=54.2 ms
-64 bytes from 10.123.114.11: icmp_seq=2 ttl=63 time=51.2 ms
-```
-
-### 3. Set up Helm
-
-Run the following command to provide Tiller with authorization to install Feast:
-
-```bash
-kubectl apply -f - <
+
+This guide will install Feast into a Kubernetes cluster on GCP. It assumes that all of your services will run within a single Kubernetes cluster. Once Feast is installed you will be able to:
+
+* Define and register features.
+* Load feature data from both batch and streaming sources.
+* Retrieve features for model training.
+* Retrieve features for online serving.
+
+{% hint style="info" %}
+This guide requires [Google Cloud Platform](https://cloud.google.com/) for installation.
+
+* [BigQuery](https://cloud.google.com/bigquery/) is used for storing historical features.
+* [Google Cloud Storage](https://cloud.google.com/storage/) is used for intermediate data storage.
+{% endhint %}
+
+## 0. Requirements
+
+1. [Google Cloud SDK ](https://cloud.google.com/sdk/install)installed, authenticated, and configured to the project you will use.
+2. [Kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) installed.
+3. [Helm](https://helm.sh/3) \(2.16.0 or greater\) installed on your local machine with Tiller installed in your cluster. Helm 3 has not been tested yet.
+
+## 1. Set up GCP
+
+First define the environmental variables that we will use throughout this installation. Please customize these to reflect your environment.
+
+```bash
+export FEAST_GCP_PROJECT_ID=my-gcp-project
+export FEAST_GCP_REGION=us-central1
+export FEAST_GCP_ZONE=us-central1-a
+export FEAST_BIGQUERY_DATASET_ID=feast
+export FEAST_GCS_BUCKET=${FEAST_GCP_PROJECT_ID}_feast_bucket
+export FEAST_GKE_CLUSTER_NAME=feast
+export FEAST_SERVICE_ACCOUNT_NAME=feast-sa
+```
+
+Create a Google Cloud Storage bucket for Feast to stage batch data exports:
+
+```bash
+gsutil mb gs://${FEAST_GCS_BUCKET}
+```
+
+Create the service account that Feast will run as:
+
+```bash
+gcloud iam service-accounts create ${FEAST_SERVICE_ACCOUNT_NAME}
+
+gcloud projects add-iam-policy-binding ${FEAST_GCP_PROJECT_ID} \
+  --member serviceAccount:${FEAST_SERVICE_ACCOUNT_NAME}@${FEAST_GCP_PROJECT_ID}.iam.gserviceaccount.com \
+  --role roles/editor
+
+gcloud iam service-accounts keys create key.json --iam-account \
+${FEAST_SERVICE_ACCOUNT_NAME}@${FEAST_GCP_PROJECT_ID}.iam.gserviceaccount.com
+```
+
+## 2. Set up a Kubernetes \(GKE\) cluster
+
+{% hint style="warning" %}
+Provisioning a GKE cluster can expose your services publicly. This guide does not cover securing access to the cluster.
+{% endhint %}
+
+Create a GKE cluster:
+
+```bash
+gcloud container clusters create ${FEAST_GKE_CLUSTER_NAME} \
+    --machine-type n1-standard-4
+```
+
+Create a secret in the GKE cluster based on your local key `key.json`:
+
+```bash
+kubectl create secret generic feast-gcp-service-account --from-file=key.json
+```
+
+For this guide we will use `NodePort` for exposing Feast services. In order to do so, we must find an External IP of at least one GKE node. This should be a public IP.
+
+```bash
+export FEAST_IP=$(kubectl describe nodes | grep ExternalIP | awk '{print $2}' | head -n 1)
+export FEAST_CORE_URL=${FEAST_IP}:32090
+export FEAST_ONLINE_SERVING_URL=${FEAST_IP}:32091
+export FEAST_BATCH_SERVING_URL=${FEAST_IP}:32092
+```
+
+Add firewall rules to open up ports on your Google Cloud Platform project:
+
+```bash
+gcloud compute firewall-rules create feast-core-port --allow tcp:32090
+gcloud compute firewall-rules create feast-online-port --allow tcp:32091
+gcloud compute firewall-rules create feast-batch-port --allow tcp:32092
+gcloud compute firewall-rules create feast-redis-port --allow tcp:32101
+gcloud compute firewall-rules create feast-kafka-ports --allow tcp:31090-31095
+```
+
+## 3. Set up Helm
+
+Run the following command to provide Tiller with authorization to install Feast:
+
+```bash
+kubectl apply -f - <\n",
+       "\n",
+       "\n",
+       "  \n",
+       "    \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "    \n",
+       "  \n",
+       "  \n",
+       "    \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "    \n",
+       "    \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "    \n",
+       "    \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "    \n",
+       "    \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "    \n",
+       "    \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "    \n",
+       "  \n",
+       "
customer_idgenderSeniorCitizenPartnerDependentstenurePhoneServiceMultipleLinesInternetServiceOnlineSecurity...DeviceProtectionTechSupportStreamingTVStreamingMoviesContractPaperlessBillingPaymentMethodMonthlyChargesTotalChargesChurn
07590-VHVEGFemale0YesNo1NoNo phone serviceDSLNo...NoNoNoNoMonth-to-monthYesElectronic check29.8529.85No
15575-GNVDEMale0NoNo34YesNoDSLYes...YesNoNoNoOne yearNoMailed check56.951889.5No
23668-QPYBKMale0NoNo2YesNoDSLYes...NoNoNoNoMonth-to-monthYesMailed check53.85108.15Yes
37795-CFOCWMale0NoNo45NoNo phone serviceDSLYes...YesYesNoNoOne yearNoBank transfer (automatic)42.301840.75No
49237-HQITUFemale0NoNo2YesNoFiber opticNo...NoNoNoNoMonth-to-monthYesElectronic check70.70151.65Yes
\n", + "

5 rows × 21 columns

\n", + "" + ], + "text/plain": [ + " customer_id gender SeniorCitizen Partner Dependents tenure PhoneService \\\n", + "0 7590-VHVEG Female 0 Yes No 1 No \n", + "1 5575-GNVDE Male 0 No No 34 Yes \n", + "2 3668-QPYBK Male 0 No No 2 Yes \n", + "3 7795-CFOCW Male 0 No No 45 No \n", + "4 9237-HQITU Female 0 No No 2 Yes \n", + "\n", + " MultipleLines InternetService OnlineSecurity ... DeviceProtection \\\n", + "0 No phone service DSL No ... No \n", + "1 No DSL Yes ... Yes \n", + "2 No DSL Yes ... No \n", + "3 No phone service DSL Yes ... Yes \n", + "4 No Fiber optic No ... No \n", + "\n", + " TechSupport StreamingTV StreamingMovies Contract PaperlessBilling \\\n", + "0 No No No Month-to-month Yes \n", + "1 No No No One year No \n", + "2 No No No Month-to-month Yes \n", + "3 Yes No No One year No \n", + "4 No No No Month-to-month Yes \n", + "\n", + " PaymentMethod MonthlyCharges TotalCharges Churn \n", + "0 Electronic check 29.85 29.85 No \n", + "1 Mailed check 56.95 1889.5 No \n", + "2 Mailed check 53.85 108.15 Yes \n", + "3 Bank transfer (automatic) 42.30 1840.75 No \n", + "4 Electronic check 70.70 151.65 Yes \n", + "\n", + "[5 rows x 21 columns]" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "telcom = pd.read_csv('http://feast-examples.storage.googleapis.com/telco-churn-xgboost/telco_customer_churn.csv')\n", + "telcom.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_uuid": "c8c010f36e29c116c6662301b08b0b0019d6e22e" + }, + "source": [ + "### 1.2 Data Manipulation" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "_uuid": "8b10c13086dff7182e399b849e31bc03df54a14e" + }, + "outputs": [], + "source": [ + "# Replacing spaces with null values in total charges column\n", + "telcom['TotalCharges'] = telcom[\"TotalCharges\"].replace(\" \",np.nan)\n", + "\n", + "# Dropping null values from total charges column which contain .15% missing data \n", + "telcom = telcom[telcom[\"TotalCharges\"].notnull()]\n", + "telcom = telcom.reset_index()[telcom.columns]\n", + "\n", + "# Convert to float type\n", + "telcom[\"TotalCharges\"] = telcom[\"TotalCharges\"].astype(float)\n", + "\n", + "# Replace 'No internet service' to No for the following columns\n", + "replace_cols = [ 'OnlineSecurity', 'OnlineBackup', 'DeviceProtection',\n", + " 'TechSupport','StreamingTV', 'StreamingMovies']\n", + "for i in replace_cols : \n", + " telcom[i] = telcom[i].replace({'No internet service' : 'No'})\n", + " \n", + "# Replace binary values with strings\n", + "telcom[\"SeniorCitizen\"] = telcom[\"SeniorCitizen\"].replace({1:\"Yes\",0:\"No\"})\n", + "\n", + "# Tenure to categorical column\n", + "def tenure_lab(telcom) :\n", + " \n", + " if telcom[\"tenure\"] <= 12 :\n", + " return \"Tenure_0-12\"\n", + " elif (telcom[\"tenure\"] > 12) & (telcom[\"tenure\"] <= 24 ):\n", + " return \"Tenure_12-24\"\n", + " elif (telcom[\"tenure\"] > 24) & (telcom[\"tenure\"] <= 48) :\n", + " return \"Tenure_24-48\"\n", + " elif (telcom[\"tenure\"] > 48) & (telcom[\"tenure\"] <= 60) :\n", + " return \"Tenure_48-60\"\n", + " elif telcom[\"tenure\"] > 60 :\n", + " return \"Tenure_gt_60\"\n", + "telcom[\"tenure_group\"] = telcom.apply(lambda telcom:tenure_lab(telcom),\n", + " axis = 1)\n", + "\n", + "# Separating churn and non churn customers\n", + "churn = telcom[telcom[\"Churn\"] == \"Yes\"]\n", + "not_churn = telcom[telcom[\"Churn\"] == \"No\"]\n", + "\n", + "# Separating catagorical and numerical columns\n", + "Id_col = ['customerID']\n", + "target_col = [\"Churn\"]\n", + "cat_cols = telcom.nunique()[telcom.nunique() < 6].keys().tolist()\n", + "cat_cols = [x for x in cat_cols if x not in target_col]\n", + "num_cols = [x for x in telcom.columns if x not in cat_cols + target_col + Id_col]\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_uuid": "6dfa77b43fe1a1a301bab65186c2a9f90245ab7d" + }, + "source": [ + "### 1.3 Data Processing" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "_uuid": "8921591320c5e336ec5a2e1efc5ed3cb0f9ec1b2" + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
customer_idgenderseniorcitizenpartnerdependentsphoneserviceonlinesecurityonlinebackupdeviceprotectiontechsupport...paymentmethod_electronic_checkpaymentmethod_mailed_checktenure_group_tenure_0_12tenure_group_tenure_12_24tenure_group_tenure_24_48tenure_group_tenure_48_60tenure_group_tenure_gt_60tenuremonthlychargestotalcharges
07590-VHVEG001000100...1010000-1.280248-1.161694-0.994194
15575-GNVDE100011010...01001000.064303-0.260878-0.173740
23668-QPYBK100011100...0110000-1.239504-0.363923-0.959649
37795-CFOCW100001011...00001000.512486-0.747850-0.195248
49237-HQITU000010000...1010000-1.2395040.196178-0.940457
\n", + "

5 rows × 35 columns

\n", + "
" + ], + "text/plain": [ + " customer_id gender seniorcitizen partner dependents phoneservice \\\n", + "0 7590-VHVEG 0 0 1 0 0 \n", + "1 5575-GNVDE 1 0 0 0 1 \n", + "2 3668-QPYBK 1 0 0 0 1 \n", + "3 7795-CFOCW 1 0 0 0 0 \n", + "4 9237-HQITU 0 0 0 0 1 \n", + "\n", + " onlinesecurity onlinebackup deviceprotection techsupport ... \\\n", + "0 0 1 0 0 ... \n", + "1 1 0 1 0 ... \n", + "2 1 1 0 0 ... \n", + "3 1 0 1 1 ... \n", + "4 0 0 0 0 ... \n", + "\n", + " paymentmethod_electronic_check paymentmethod_mailed_check \\\n", + "0 1 0 \n", + "1 0 1 \n", + "2 0 1 \n", + "3 0 0 \n", + "4 1 0 \n", + "\n", + " tenure_group_tenure_0_12 tenure_group_tenure_12_24 \\\n", + "0 1 0 \n", + "1 0 0 \n", + "2 1 0 \n", + "3 0 0 \n", + "4 1 0 \n", + "\n", + " tenure_group_tenure_24_48 tenure_group_tenure_48_60 \\\n", + "0 0 0 \n", + "1 1 0 \n", + "2 0 0 \n", + "3 1 0 \n", + "4 0 0 \n", + "\n", + " tenure_group_tenure_gt_60 tenure monthlycharges totalcharges \n", + "0 0 -1.280248 -1.161694 -0.994194 \n", + "1 0 0.064303 -0.260878 -0.173740 \n", + "2 0 -1.239504 -0.363923 -0.959649 \n", + "3 0 0.512486 -0.747850 -0.195248 \n", + "4 0 -1.239504 0.196178 -0.940457 \n", + "\n", + "[5 rows x 35 columns]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from sklearn.preprocessing import LabelEncoder\n", + "from sklearn.preprocessing import StandardScaler\n", + "\n", + "# Customer id col\n", + "Id_col = ['customer_id']\n", + "\n", + "# Target columns\n", + "target_col = [\"churn\"]\n", + "\n", + "# Categorical columns\n", + "cat_cols = telcom.nunique()[telcom.nunique() < 6].keys().tolist()\n", + "cat_cols = [x for x in cat_cols if x not in target_col]\n", + "\n", + "# Numerical columns\n", + "num_cols = [x for x in telcom.columns if x not in cat_cols + target_col + Id_col]\n", + "\n", + "# Binary columns with 2 values\n", + "bin_cols = telcom.nunique()[telcom.nunique() == 2].keys().tolist()\n", + "\n", + "# Columns more than 2 values\n", + "multi_cols = [i for i in cat_cols if i not in bin_cols]\n", + "\n", + "# Label encoding Binary columns\n", + "le = LabelEncoder()\n", + "for i in bin_cols :\n", + " telcom[i] = le.fit_transform(telcom[i])\n", + " \n", + "# Duplicating columns for multi value columns\n", + "telcom = pd.get_dummies(data = telcom,columns = multi_cols )\n", + "\n", + "# Scaling Numerical columns\n", + "std = StandardScaler()\n", + "scaled = std.fit_transform(telcom[num_cols])\n", + "scaled = pd.DataFrame(scaled,columns=num_cols)\n", + "\n", + "# Dropping original values merging scaled values for numerical columns\n", + "df_telcom_og = telcom.copy()\n", + "telcom = telcom.drop(columns = num_cols,axis = 1)\n", + "telcom = telcom.merge(scaled,left_index=True,right_index=True,how = \"left\")\n", + "\n", + "# Clean up column names\n", + "telcom.columns = [slugify(col, lowercase=True, separator='_') for col in telcom.columns]\n", + "telcom.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_uuid": "9ec25cff71c0eb0f0c839a726cb06cb43462a53f" + }, + "source": [ + "### 1.4 Descriptive Statistics" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
countuniquetopfreqmeanstdmin25%50%75%max
customer_id703270320835-DUUIQ1NaNNaNNaNNaNNaNNaNNaN
gender7032NaNNaNNaN0.5046930.50001400111
seniorcitizen7032NaNNaNNaN0.16240.36884400001
partner7032NaNNaNNaN0.4825090.49972900011
dependents7032NaNNaNNaN0.2984930.45762900011
phoneservice7032NaNNaNNaN0.9032990.29557101111
onlinesecurity7032NaNNaNNaN0.2865470.4521800011
onlinebackup7032NaNNaNNaN0.3448520.47535400011
deviceprotection7032NaNNaNNaN0.3438570.47502800011
techsupport7032NaNNaNNaN0.2901020.45384200011
streamingtv7032NaNNaNNaN0.3843860.48648400011
streamingmovies7032NaNNaNNaN0.3883670.48741400011
paperlessbilling7032NaNNaNNaN0.5927190.49136300111
churn7032NaNNaNNaN0.2657850.44178200011
multiplelines_no7032NaNNaNNaN0.4813710.49968800011
multiplelines_no_phone_service7032NaNNaNNaN0.09670080.29557100001
multiplelines_yes7032NaNNaNNaN0.4219280.49390200011
internetservice_dsl7032NaNNaNNaN0.3435720.47493400011
internetservice_fiber_optic7032NaNNaNNaN0.4402730.49645500011
internetservice_no7032NaNNaNNaN0.2161550.4116500001
contract_month_to_month7032NaNNaNNaN0.5510520.49742200111
contract_one_year7032NaNNaNNaN0.2093290.40685800001
contract_two_year7032NaNNaNNaN0.2396190.42688100001
paymentmethod_bank_transfer_automatic7032NaNNaNNaN0.2192830.4137900001
paymentmethod_credit_card_automatic7032NaNNaNNaN0.2162970.41174800001
paymentmethod_electronic_check7032NaNNaNNaN0.336320.47248300011
paymentmethod_mailed_check7032NaNNaNNaN0.22810.41963700001
tenure_group_tenure_0_127032NaNNaNNaN0.30930.46223800011
tenure_group_tenure_12_247032NaNNaNNaN0.145620.3527500001
tenure_group_tenure_24_487032NaNNaNNaN0.2266780.41871200001
tenure_group_tenure_48_607032NaNNaNNaN0.1183160.32300500001
tenure_group_tenure_gt_607032NaNNaNNaN0.2000850.40009200001
tenure7032NaNNaNNaN-1.12664e-161.00007-1.28025-0.954296-0.1394170.9199261.61257
monthlycharges7032NaNNaNNaN6.06265e-171.00007-1.54728-0.9709770.1845440.8331481.79338
totalcharges7032NaNNaNNaN-1.11906e-161.00007-0.999069-0.830249-0.3908150.6668272.82426
\n", + "
" + ], + "text/plain": [ + " count unique top freq \\\n", + "customer_id 7032 7032 0835-DUUIQ 1 \n", + "gender 7032 NaN NaN NaN \n", + "seniorcitizen 7032 NaN NaN NaN \n", + "partner 7032 NaN NaN NaN \n", + "dependents 7032 NaN NaN NaN \n", + "phoneservice 7032 NaN NaN NaN \n", + "onlinesecurity 7032 NaN NaN NaN \n", + "onlinebackup 7032 NaN NaN NaN \n", + "deviceprotection 7032 NaN NaN NaN \n", + "techsupport 7032 NaN NaN NaN \n", + "streamingtv 7032 NaN NaN NaN \n", + "streamingmovies 7032 NaN NaN NaN \n", + "paperlessbilling 7032 NaN NaN NaN \n", + "churn 7032 NaN NaN NaN \n", + "multiplelines_no 7032 NaN NaN NaN \n", + "multiplelines_no_phone_service 7032 NaN NaN NaN \n", + "multiplelines_yes 7032 NaN NaN NaN \n", + "internetservice_dsl 7032 NaN NaN NaN \n", + "internetservice_fiber_optic 7032 NaN NaN NaN \n", + "internetservice_no 7032 NaN NaN NaN \n", + "contract_month_to_month 7032 NaN NaN NaN \n", + "contract_one_year 7032 NaN NaN NaN \n", + "contract_two_year 7032 NaN NaN NaN \n", + "paymentmethod_bank_transfer_automatic 7032 NaN NaN NaN \n", + "paymentmethod_credit_card_automatic 7032 NaN NaN NaN \n", + "paymentmethod_electronic_check 7032 NaN NaN NaN \n", + "paymentmethod_mailed_check 7032 NaN NaN NaN \n", + "tenure_group_tenure_0_12 7032 NaN NaN NaN \n", + "tenure_group_tenure_12_24 7032 NaN NaN NaN \n", + "tenure_group_tenure_24_48 7032 NaN NaN NaN \n", + "tenure_group_tenure_48_60 7032 NaN NaN NaN \n", + "tenure_group_tenure_gt_60 7032 NaN NaN NaN \n", + "tenure 7032 NaN NaN NaN \n", + "monthlycharges 7032 NaN NaN NaN \n", + "totalcharges 7032 NaN NaN NaN \n", + "\n", + " mean std min \\\n", + "customer_id NaN NaN NaN \n", + "gender 0.504693 0.500014 0 \n", + "seniorcitizen 0.1624 0.368844 0 \n", + "partner 0.482509 0.499729 0 \n", + "dependents 0.298493 0.457629 0 \n", + "phoneservice 0.903299 0.295571 0 \n", + "onlinesecurity 0.286547 0.45218 0 \n", + "onlinebackup 0.344852 0.475354 0 \n", + "deviceprotection 0.343857 0.475028 0 \n", + "techsupport 0.290102 0.453842 0 \n", + "streamingtv 0.384386 0.486484 0 \n", + "streamingmovies 0.388367 0.487414 0 \n", + "paperlessbilling 0.592719 0.491363 0 \n", + "churn 0.265785 0.441782 0 \n", + "multiplelines_no 0.481371 0.499688 0 \n", + "multiplelines_no_phone_service 0.0967008 0.295571 0 \n", + "multiplelines_yes 0.421928 0.493902 0 \n", + "internetservice_dsl 0.343572 0.474934 0 \n", + "internetservice_fiber_optic 0.440273 0.496455 0 \n", + "internetservice_no 0.216155 0.41165 0 \n", + "contract_month_to_month 0.551052 0.497422 0 \n", + "contract_one_year 0.209329 0.406858 0 \n", + "contract_two_year 0.239619 0.426881 0 \n", + "paymentmethod_bank_transfer_automatic 0.219283 0.41379 0 \n", + "paymentmethod_credit_card_automatic 0.216297 0.411748 0 \n", + "paymentmethod_electronic_check 0.33632 0.472483 0 \n", + "paymentmethod_mailed_check 0.2281 0.419637 0 \n", + "tenure_group_tenure_0_12 0.3093 0.462238 0 \n", + "tenure_group_tenure_12_24 0.14562 0.35275 0 \n", + "tenure_group_tenure_24_48 0.226678 0.418712 0 \n", + "tenure_group_tenure_48_60 0.118316 0.323005 0 \n", + "tenure_group_tenure_gt_60 0.200085 0.400092 0 \n", + "tenure -1.12664e-16 1.00007 -1.28025 \n", + "monthlycharges 6.06265e-17 1.00007 -1.54728 \n", + "totalcharges -1.11906e-16 1.00007 -0.999069 \n", + "\n", + " 25% 50% 75% max \n", + "customer_id NaN NaN NaN NaN \n", + "gender 0 1 1 1 \n", + "seniorcitizen 0 0 0 1 \n", + "partner 0 0 1 1 \n", + "dependents 0 0 1 1 \n", + "phoneservice 1 1 1 1 \n", + "onlinesecurity 0 0 1 1 \n", + "onlinebackup 0 0 1 1 \n", + "deviceprotection 0 0 1 1 \n", + "techsupport 0 0 1 1 \n", + "streamingtv 0 0 1 1 \n", + "streamingmovies 0 0 1 1 \n", + "paperlessbilling 0 1 1 1 \n", + "churn 0 0 1 1 \n", + "multiplelines_no 0 0 1 1 \n", + "multiplelines_no_phone_service 0 0 0 1 \n", + "multiplelines_yes 0 0 1 1 \n", + "internetservice_dsl 0 0 1 1 \n", + "internetservice_fiber_optic 0 0 1 1 \n", + "internetservice_no 0 0 0 1 \n", + "contract_month_to_month 0 1 1 1 \n", + "contract_one_year 0 0 0 1 \n", + "contract_two_year 0 0 0 1 \n", + "paymentmethod_bank_transfer_automatic 0 0 0 1 \n", + "paymentmethod_credit_card_automatic 0 0 0 1 \n", + "paymentmethod_electronic_check 0 0 1 1 \n", + "paymentmethod_mailed_check 0 0 0 1 \n", + "tenure_group_tenure_0_12 0 0 1 1 \n", + "tenure_group_tenure_12_24 0 0 0 1 \n", + "tenure_group_tenure_24_48 0 0 0 1 \n", + "tenure_group_tenure_48_60 0 0 0 1 \n", + "tenure_group_tenure_gt_60 0 0 0 1 \n", + "tenure -0.954296 -0.139417 0.919926 1.61257 \n", + "monthlycharges -0.970977 0.184544 0.833148 1.79338 \n", + "totalcharges -0.830249 -0.390815 0.666827 2.82426 " + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "telcom.describe(include='all').T" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_uuid": "82a7617e37906622dbe00a1783a13cbf382d2513" + }, + "source": [ + "### 1.5 Correlation Matrix" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "_uuid": "b52cf9c7f402ed706e82221e3f8601fdeea9ab27" + }, + "outputs": [ + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "linkText": "Export to plot.ly", + "plotlyServerURL": "https://plot.ly", + "showLink": false + }, + "data": [ + { + "colorbar": { + "title": { + "side": "right", + "text": "Pearson Correlation coefficient" + } + }, + "colorscale": [ + [ + 0, + "#440154" + ], + [ + 0.1111111111111111, + "#482878" + ], + [ + 0.2222222222222222, + "#3e4989" + ], + [ + 0.3333333333333333, + "#31688e" + ], + [ + 0.4444444444444444, + "#26828e" + ], + [ + 0.5555555555555556, + "#1f9e89" + ], + [ + 0.6666666666666666, + "#35b779" + ], + [ + 0.7777777777777778, + "#6ece58" + ], + [ + 0.8888888888888888, + "#b5de2b" + ], + [ + 1, + "#fde725" + ] + ], + "type": "heatmap", + "x": [ + "gender", + "seniorcitizen", + "partner", + "dependents", + "phoneservice", + "onlinesecurity", + "onlinebackup", + "deviceprotection", + "techsupport", + "streamingtv", + "streamingmovies", + "paperlessbilling", + "churn", + "multiplelines_no", + "multiplelines_no_phone_service", + "multiplelines_yes", + "internetservice_dsl", + "internetservice_fiber_optic", + "internetservice_no", + "contract_month_to_month", + "contract_one_year", + "contract_two_year", + "paymentmethod_bank_transfer_automatic", + "paymentmethod_credit_card_automatic", + "paymentmethod_electronic_check", + "paymentmethod_mailed_check", + "tenure_group_tenure_0_12", + "tenure_group_tenure_12_24", + "tenure_group_tenure_24_48", + "tenure_group_tenure_48_60", + "tenure_group_tenure_gt_60", + "tenure", + "monthlycharges", + "totalcharges" + ], + "y": [ + "gender", + "seniorcitizen", + "partner", + "dependents", + "phoneservice", + "onlinesecurity", + "onlinebackup", + "deviceprotection", + "techsupport", + "streamingtv", + "streamingmovies", + "paperlessbilling", + "churn", + "multiplelines_no", + "multiplelines_no_phone_service", + "multiplelines_yes", + "internetservice_dsl", + "internetservice_fiber_optic", + "internetservice_no", + "contract_month_to_month", + "contract_one_year", + "contract_two_year", + "paymentmethod_bank_transfer_automatic", + "paymentmethod_credit_card_automatic", + "paymentmethod_electronic_check", + "paymentmethod_mailed_check", + "tenure_group_tenure_0_12", + "tenure_group_tenure_12_24", + "tenure_group_tenure_24_48", + "tenure_group_tenure_48_60", + "tenure_group_tenure_gt_60", + "tenure", + "monthlycharges", + "totalcharges" + ], + "z": [ + [ + 1, + -0.001819390613419179, + -0.0013790513218356025, + 0.010348917127614397, + -0.007514979909200033, + -0.01632782307070617, + -0.013092839264555001, + -0.0008067457759124324, + -0.008507162405232782, + -0.0071243969867245535, + -0.010105418366566195, + -0.011901894766838502, + -0.008544643224947218, + 0.0043346914502916285, + 0.0075149799091999425, + -0.008882737146286056, + 0.00758357610303961, + -0.011189259276385864, + 0.004744965758849955, + -0.0032507651194551004, + 0.0077548529142672145, + -0.003603167413572989, + -0.015973079031173835, + 0.001631872518613598, + 0.0008437084888753327, + 0.01319936726545174, + -0.0010503798619876774, + -0.0006494991207074467, + -0.010516394125351592, + -0.004318975744551275, + 0.01627881894278637, + 0.005285371870295646, + -0.013779327268354416, + 4.783950839776602e-05 + ], + [ + -0.001819390613419179, + 1, + 0.01695661453202187, + -0.21055006112684216, + 0.008391611911217043, + -0.0385763901686064, + 0.06666279065142021, + 0.059513871482029225, + -0.060576839406188035, + 0.10544501753678828, + 0.11984236746151568, + 0.15625775052783097, + 0.1505410534156757, + -0.13637672686229402, + -0.008391611911217034, + 0.14299625086621018, + -0.10827563872848943, + 0.25492331502717946, + -0.18251949495535458, + 0.13775207088551514, + -0.0464907545657889, + -0.11620511425710835, + -0.01623474200582214, + -0.024359419683712323, + 0.17132216591713703, + -0.15298719260173027, + -0.02771322371899571, + 0.0018604411671567017, + 0.02038346087056509, + 0.01418568213097567, + -0.0024069937431968856, + 0.01568347989913396, + 0.21987422950593646, + 0.10241060539532633 + ], + [ + -0.0013790513218356025, + 0.01695661453202187, + 1, + 0.45226888584550023, + 0.018397189302703662, + 0.14334606167364233, + 0.14184917072520303, + 0.1535564364182745, + 0.12020601780298455, + 0.12448262672518244, + 0.11810820943292764, + -0.013956696136191696, + -0.14998192562006138, + -0.13002839561671578, + -0.018397189302703666, + 0.1425612874681736, + -0.0010430787434336079, + 0.0012346095228208073, + -0.0002855204740384597, + -0.2802019157901561, + 0.08306706395255747, + 0.24733370647615796, + 0.11140561212215645, + 0.08232738919649572, + -0.08320661736633733, + -0.09694798339506473, + -0.30506147447521575, + -0.048481275955609554, + 0.028467762106169584, + 0.10534126166196492, + 0.28035324824529767, + 0.38191150910757077, + 0.09782497186892049, + 0.31907236323857324 + ], + [ + 0.010348917127614397, + -0.21055006112684216, + 0.45226888584550023, + 1, + -0.001077812708067608, + 0.08078553224088346, + 0.023638813060609963, + 0.013899668260943368, + 0.06305315799997827, + -0.01649868035801052, + -0.03837492560091315, + -0.11013068597336993, + -0.16312843938822, + 0.023387669506115295, + 0.0010778127080674446, + -0.024306661314620996, + 0.05159321756471237, + -0.16410089031864167, + 0.13838288994798562, + -0.2297147909213749, + 0.06922205672629726, + 0.2016993304297039, + 0.05236890928127545, + 0.06113408322897806, + -0.1492739811934336, + 0.05644841359590588, + -0.1453791450700764, + -0.0014594010172321779, + 0.02464494581436856, + 0.03141940765317757, + 0.1180897178470907, + 0.16338596691556453, + -0.11234295350128225, + 0.0646532494217739 + ], + [ + -0.007514979909200033, + 0.008391611911217043, + 0.018397189302703662, + -0.001077812708067608, + 1, + -0.09167570469500017, + -0.05213341919796151, + -0.07007561533228862, + -0.09513849428922488, + -0.021382711870578185, + -0.03347749718236395, + 0.016696123642784135, + 0.011691398865422323, + 0.31521775126801543, + -1, + 0.2795295400049995, + -0.45225528090657086, + 0.29018311793843365, + 0.1718171065699321, + -0.0012425134067734023, + -0.0031417807184111624, + 0.0044422513152849235, + 0.008271245210923577, + -0.006916252198127548, + 0.0027471183312986857, + -0.004462839400732194, + -0.00694955039725527, + 0.012306612692869211, + -0.014777815951004124, + -0.009750281447949313, + 0.020515848357693, + 0.007877333295041818, + 0.2480330664757158, + 0.11300826095473893 + ], + [ + -0.01632782307070617, + -0.0385763901686064, + 0.14334606167364233, + 0.08078553224088346, + -0.09167570469500017, + 1, + 0.28328454262626757, + 0.274875003842449, + 0.35445796164509147, + 0.1755144708953688, + 0.18742584957299618, + -0.004051250607988492, + -0.17126992353351678, + -0.15167751168612081, + 0.09167570469500017, + 0.0985919934252315, + 0.3203433737595294, + -0.03050626904076453, + -0.33279949932167546, + -0.24684428487400414, + 0.10065777311969464, + 0.19169819815673583, + 0.09436639279979232, + 0.11547320256635303, + -0.11229466175861408, + -0.07991768713640306, + -0.24240887436050382, + -0.0556619409198579, + 0.0076950987178344265, + 0.0716632710172793, + 0.26322845571231857, + 0.32829748818662485, + 0.2964469592375873, + 0.41261876950713655 + ], + [ + -0.013092839264555001, + 0.06666279065142021, + 0.14184917072520303, + 0.023638813060609963, + -0.05213341919796151, + 0.28328454262626757, + 1, + 0.30305766643807824, + 0.29370469187781045, + 0.2816010622259753, + 0.27452301070772545, + 0.12705603268686044, + -0.08230696876508349, + -0.2307241996493635, + 0.05213341919796151, + 0.2022283972825372, + 0.15676460995441888, + 0.16594028590307677, + -0.3809903317320751, + -0.16439302987919688, + 0.08411316021806066, + 0.11139068731904943, + 0.0869415675760231, + 0.09045518641091457, + -0.00036426636786500763, + -0.17407470231312427, + -0.26736609150996915, + -0.08408097138202993, + 0.023085000444599418, + 0.09919210454041022, + 0.2787875817043761, + 0.36113847824658735, + 0.4415290881871007, + 0.510100290145439 + ], + [ + -0.0008067457759124324, + 0.059513871482029225, + 0.1535564364182745, + 0.013899668260943368, + -0.07007561533228862, + 0.274875003842449, + 0.30305766643807824, + 1, + 0.33285005080469243, + 0.3899237975094597, + 0.4023088228216018, + 0.10407904724402045, + -0.06619251684228997, + -0.24084736328107104, + 0.07007561533228862, + 0.201732824517757, + 0.14514955473692903, + 0.17635617323471664, + -0.3801513548956378, + -0.22598757731112262, + 0.10291089629343353, + 0.16524753554250074, + 0.08304690185342707, + 0.11125168129784246, + -0.003308493511411254, + -0.18732483013668594, + -0.2739200544387702, + -0.07733200729381666, + 0.0449724925036831, + 0.07685425917962475, + 0.2755370112516221, + 0.3615199952862194, + 0.48260691224313995, + 0.5228814865154369 + ], + [ + -0.008507162405232782, + -0.060576839406188035, + 0.12020601780298455, + 0.06305315799997827, + -0.09513849428922488, + 0.35445796164509147, + 0.29370469187781045, + 0.33285005080469243, + 1, + 0.277548599200549, + 0.2801552432906342, + 0.03753587307318976, + -0.16471590834411207, + -0.15553386914722026, + 0.09513849428922488, + 0.10042125595413272, + 0.3121832985222757, + -0.020298967520709605, + -0.33569508671869736, + -0.28549086901033593, + 0.09625836225380952, + 0.24092408252256528, + 0.10047200087443556, + 0.11702370730984143, + -0.11480726996437085, + -0.08463055196615278, + -0.23862820953475447, + -0.07201850701116608, + 0.022136746625123563, + 0.06270980841796413, + 0.26539627802277554, + 0.325288454100009, + 0.33830139143424953, + 0.4328683682410939 + ], + [ + -0.0071243969867245535, + 0.10544501753678828, + 0.12448262672518244, + -0.01649868035801052, + -0.021382711870578185, + 0.1755144708953688, + 0.2816010622259753, + 0.3899237975094597, + 0.277548599200549, + 1, + 0.5333800979319763, + 0.22424119793848596, + 0.06325398027519404, + -0.26746641467807136, + 0.021382711870577976, + 0.25780350066730157, + 0.014973379079172382, + 0.3297441152730512, + -0.41495062156578044, + -0.11254989712217289, + 0.061929689963200855, + 0.07212357537070835, + 0.04612070051242859, + 0.040010276337768505, + 0.1447470086556032, + -0.2477115493728633, + -0.22076087097198827, + -0.050234026791169964, + 0.02882937792536747, + 0.08796930347195206, + 0.1981501311372027, + 0.280263628074823, + 0.6296678921767406, + 0.5157090769923935 + ], + [ + -0.010105418366566195, + 0.11984236746151568, + 0.11810820943292764, + -0.03837492560091315, + -0.03347749718236395, + 0.18742584957299618, + 0.27452301070772545, + 0.4023088228216018, + 0.2801552432906342, + 0.5333800979319763, + 1, + 0.21158250423808916, + 0.06085993668146301, + -0.2759953197780627, + 0.03347749718236395, + 0.2591943175468362, + 0.02562310861129719, + 0.3224574540559222, + -0.41844975538334045, + -0.11786687989290552, + 0.06477997824381859, + 0.07560257919382919, + 0.04875484714119087, + 0.048398314068082315, + 0.13742008269944622, + -0.2502897149395328, + -0.2213881765552116, + -0.054338325985925064, + 0.02504810987080452, + 0.08661557153323526, + 0.20754366554274195, + 0.2854022671060787, + 0.6272347301103788, + 0.5198665357835017 + ], + [ + -0.011901894766838502, + 0.15625775052783097, + -0.013956696136191696, + -0.11013068597336993, + 0.016696123642784135, + -0.004051250607988492, + 0.12705603268686044, + 0.10407904724402045, + 0.03753587307318976, + 0.22424119793848596, + 0.21158250423808916, + 1, + 0.19145432108006671, + -0.1519737778021082, + -0.016696123642784142, + 0.1637457730112591, + -0.06338966821876392, + 0.32647017160380964, + -0.3205922451174622, + 0.16829626845602835, + -0.052278164693773076, + -0.1462807050684952, + -0.017468900682392235, + -0.013726285095880284, + 0.20842668228002995, + -0.20398064814312206, + -0.003859801258113621, + 0.003328006906514837, + -0.005388027856510724, + 0.010626123397145876, + -0.001414839543993747, + 0.004823156615386272, + 0.3519304153712528, + 0.15782978286591698 + ], + [ + -0.008544643224947218, + 0.1505410534156757, + -0.14998192562006138, + -0.16312843938822, + 0.011691398865422323, + -0.17126992353351678, + -0.08230696876508349, + -0.06619251684228997, + -0.16471590834411207, + 0.06325398027519404, + 0.06085993668146301, + 0.19145432108006671, + 1, + -0.03265360299730401, + -0.01169139886542221, + 0.040032739872523634, + -0.12414142842590645, + 0.30746259069818205, + -0.22757762044656818, + 0.40456455007784087, + -0.17822502328994053, + -0.30155233962397837, + -0.1181359978280296, + -0.1346868372340906, + 0.30145463790858057, + -0.09077284582582087, + 0.3196275743451608, + 0.019928968647699895, + -0.07585881574899747, + -0.10079964105628228, + -0.2260781096915624, + -0.3540493589532626, + 0.1928582184700881, + -0.1994840835675715 + ], + [ + 0.0043346914502916285, + -0.13637672686229402, + -0.13002839561671578, + 0.023387669506115295, + 0.31521775126801543, + -0.15167751168612081, + -0.2307241996493635, + -0.24084736328107104, + -0.15553386914722026, + -0.26746641467807136, + -0.2759953197780627, + -0.1519737778021082, + -0.03265360299730401, + 1, + -0.3152177512680155, + -0.8230760279128654, + -0.06951498798055156, + -0.19053099387424138, + 0.3099843327828031, + 0.08679791861868207, + 0.0016944436455045328, + -0.1027560050227385, + -0.06966277653167001, + -0.06371157499118037, + -0.0809902106781048, + 0.22239547449983668, + 0.25617128287621094, + 0.05170274575687212, + -0.03215707580783382, + -0.08063023581160907, + -0.2427981631428492, + -0.323890766966285, + -0.3385136039067215, + -0.39676537714107735 + ], + [ + 0.0075149799091999425, + -0.008391611911217034, + -0.018397189302703666, + 0.0010778127080674446, + -1, + 0.09167570469500017, + 0.05213341919796151, + 0.07007561533228862, + 0.09513849428922488, + 0.021382711870577976, + 0.03347749718236395, + -0.016696123642784142, + -0.01169139886542221, + -0.3152177512680155, + 1, + -0.2795295400049995, + 0.45225528090657086, + -0.29018311793843365, + -0.1718171065699321, + 0.0012425134067734103, + 0.0031417807184113207, + -0.004442251315284906, + -0.008271245210923579, + 0.006916252198127564, + -0.0027471183312986857, + 0.004462839400732508, + 0.006949550397255344, + -0.012306612692869208, + 0.014777815951004133, + 0.009750281447949528, + -0.02051584835769357, + -0.007877333295041807, + -0.2480330664757158, + -0.1130082609547389 + ], + [ + -0.008882737146286056, + 0.14299625086621018, + 0.1425612874681736, + -0.024306661314620996, + 0.2795295400049995, + 0.0985919934252315, + 0.2022283972825372, + 0.201732824517757, + 0.10042125595413272, + 0.25780350066730157, + 0.2591943175468362, + 0.1637457730112591, + 0.040032739872523634, + -0.8230760279128654, + -0.2795295400049995, + 1, + -0.2003183214156725, + 0.3664202566051166, + -0.21079354712189471, + -0.08855832218643561, + -0.003594461398434266, + 0.10661820819152797, + 0.07542871730303179, + 0.06031899094084278, + 0.08358299305536028, + -0.22767156949803466, + -0.26333120743722876, + -0.04494367118136859, + 0.023690157563696343, + 0.07573985650934924, + 0.25792003827196397, + 0.33239924473562554, + 0.4909121973267493, + 0.4690421356971826 + ], + [ + 0.00758357610303961, + -0.10827563872848943, + -0.0010430787434336079, + 0.05159321756471237, + -0.45225528090657086, + 0.3203433737595294, + 0.15676460995441888, + 0.14514955473692903, + 0.3121832985222757, + 0.014973379079172382, + 0.02562310861129719, + -0.06338966821876392, + -0.12414142842590645, + -0.06951498798055156, + 0.45225528090657086, + -0.2003183214156725, + 1, + -0.6416356650534906, + -0.3799117751052334, + -0.06522632996531619, + 0.04729967349374622, + 0.030923714855574744, + 0.02475954021490298, + 0.05122176476699011, + -0.10429333541089097, + 0.04275388869901973, + -0.0014704079413737543, + -0.013428683581908657, + -0.0004678565419111244, + -0.00079001062411148, + 0.014665913237832425, + 0.013786269825793156, + -0.16136793538251534, + -0.052189866637419764 + ], + [ + -0.011189259276385864, + 0.25492331502717946, + 0.0012346095228208073, + -0.16410089031864167, + 0.29018311793843365, + -0.03050626904076453, + 0.16594028590307677, + 0.17635617323471664, + -0.020298967520709605, + 0.3297441152730512, + 0.3224574540559222, + 0.32647017160380964, + 0.30746259069818205, + -0.19053099387424138, + -0.29018311793843365, + 0.3664202566051166, + -0.6416356650534906, + 1, + -0.4657363343235562, + 0.24301351814831268, + -0.07680902975096314, + -0.20996452908904545, + -0.022778855291876635, + -0.050551991558036385, + 0.3357634768102139, + -0.3059839224841771, + -0.021440560786684968, + -0.0014940221824271288, + 0.005613761063888361, + 0.01746632456448475, + 0.0061120739544805805, + 0.017929529906438778, + 0.7871948529419658, + 0.3607687920142481 + ], + [ + 0.004744965758849955, + -0.18251949495535458, + -0.0002855204740384597, + 0.13838288994798562, + 0.1718171065699321, + -0.33279949932167546, + -0.3809903317320751, + -0.3801513548956378, + -0.33569508671869736, + -0.41495062156578044, + -0.41844975538334045, + -0.3205922451174622, + -0.22757762044656818, + 0.3099843327828031, + -0.1718171065699321, + -0.21079354712189471, + -0.3799117751052334, + -0.4657363343235562, + 1, + -0.217823505545489, + 0.038061459724733744, + 0.21754205606911817, + -0.0010943032992050863, + 0.0018701145623306124, + -0.2846082097225956, + 0.3196937439459745, + 0.027554030004254233, + 0.01729491162511594, + -0.006230482005525746, + -0.020153136101165186, + -0.024291768931540957, + -0.037528889581619734, + -0.7631910615169571, + -0.37487836259896 + ], + [ + -0.0032507651194551004, + 0.13775207088551514, + -0.2802019157901561, + -0.2297147909213749, + -0.0012425134067734023, + -0.24684428487400414, + -0.16439302987919688, + -0.22598757731112262, + -0.28549086901033593, + -0.11254989712217289, + -0.11786687989290552, + 0.16829626845602835, + 0.40456455007784087, + 0.08679791861868207, + 0.0012425134067734103, + -0.08855832218643561, + -0.06522632996531619, + 0.24301351814831268, + -0.217823505545489, + 1, + -0.5700527848944215, + -0.6219327447713561, + -0.18015909738683936, + -0.20496021669474843, + 0.33087881370583405, + 0.006208692442055045, + 0.49205202568054424, + 0.14000368638519403, + -0.05215631834144274, + -0.19870886170688193, + -0.47691223651608966, + -0.649345648869048, + 0.0589334582292538, + -0.4467758743246882 + ], + [ + 0.0077548529142672145, + -0.0464907545657889, + 0.08306706395255747, + 0.06922205672629726, + -0.0031417807184111624, + 0.10065777311969464, + 0.08411316021806066, + 0.10291089629343353, + 0.09625836225380952, + 0.061929689963200855, + 0.06477997824381859, + -0.052278164693773076, + -0.17822502328994053, + 0.0016944436455045328, + 0.0031417807184113207, + -0.003594461398434266, + 0.04729967349374622, + -0.07680902975096314, + 0.038061459724733744, + -0.5700527848944215, + 1, + -0.28884268256780254, + 0.05762874929825631, + 0.06758968145792751, + -0.10954646682258033, + 0.0001971241890499786, + -0.2512993032332009, + -0.017196458507178478, + 0.15389327309228798, + 0.15891681528381144, + 0.01614171428694319, + 0.2023384083649546, + 0.004809615120440435, + 0.17056928655718714 + ], + [ + -0.003603167413572989, + -0.11620511425710835, + 0.24733370647615796, + 0.2016993304297039, + 0.0044422513152849235, + 0.19169819815673583, + 0.11139068731904943, + 0.16524753554250074, + 0.24092408252256528, + 0.07212357537070835, + 0.07560257919382919, + -0.1462807050684952, + -0.30155233962397837, + -0.1027560050227385, + -0.004442251315284906, + 0.10661820819152797, + 0.030923714855574744, + -0.20996452908904545, + 0.21754205606911817, + -0.6219327447713561, + -0.28884268256780254, + 1, + 0.1550042180767056, + 0.17440993895950943, + -0.28114743393395547, + -0.007422540118618717, + -0.33385014178843664, + -0.14674905438081498, + -0.08589992270093438, + 0.08008211690632902, + 0.5403360993105428, + 0.5638005002286687, + -0.07325607300641665, + 0.35803561609140894 + ], + [ + -0.015973079031173835, + -0.01623474200582214, + 0.11140561212215645, + 0.05236890928127545, + 0.008271245210923577, + 0.09436639279979232, + 0.0869415675760231, + 0.08304690185342707, + 0.10047200087443556, + 0.04612070051242859, + 0.04875484714119087, + -0.017468900682392235, + -0.1181359978280296, + -0.06966277653167001, + -0.008271245210923579, + 0.07542871730303179, + 0.02475954021490298, + -0.022778855291876635, + -0.0010943032992050863, + -0.18015909738683936, + 0.05762874929825631, + 0.1550042180767056, + 1, + -0.27842319712065355, + -0.3772703602158491, + -0.28809669563791657, + -0.18585512574019983, + -0.04632871286727803, + 0.02172285552026243, + 0.060183035947993645, + 0.1842491799195877, + 0.24382246495742915, + 0.04240972759459293, + 0.18611944938877964 + ], + [ + 0.001631872518613598, + -0.024359419683712323, + 0.08232738919649572, + 0.06113408322897806, + -0.006916252198127548, + 0.11547320256635303, + 0.09045518641091457, + 0.11125168129784246, + 0.11702370730984143, + 0.040010276337768505, + 0.048398314068082315, + -0.013726285095880284, + -0.1346868372340906, + -0.06371157499118037, + 0.006916252198127564, + 0.06031899094084278, + 0.05122176476699011, + -0.050551991558036385, + 0.0018701145623306124, + -0.20496021669474843, + 0.06758968145792751, + 0.17440993895950943, + -0.27842319712065355, + 1, + -0.37397801626928234, + -0.28558254792863036, + -0.18416488323856553, + -0.03964706284937272, + 0.028232511385228868, + 0.048166948994686994, + 0.17929367300430438, + 0.23280034413314615, + 0.030054608328366407, + 0.1826633671546002 + ], + [ + 0.0008437084888753327, + 0.17132216591713703, + -0.08320661736633733, + -0.1492739811934336, + 0.0027471183312986857, + -0.11229466175861408, + -0.00036426636786500763, + -0.003308493511411254, + -0.11480726996437085, + 0.1447470086556032, + 0.13742008269944622, + 0.20842668228002995, + 0.30145463790858057, + -0.0809902106781048, + -0.0027471183312986857, + 0.08358299305536028, + -0.10429333541089097, + 0.3357634768102139, + -0.2846082097225956, + 0.33087881370583405, + -0.10954646682258033, + -0.28114743393395547, + -0.3772703602158491, + -0.37397801626928234, + 1, + -0.3869714587097063, + 0.1605298863682866, + 0.030386796069204806, + -0.011569992641797876, + -0.030584308144429393, + -0.1754558932547761, + -0.21019749610817867, + 0.27111737739356384, + -0.060436278689198375 + ], + [ + 0.01319936726545174, + -0.15298719260173027, + -0.09694798339506473, + 0.05644841359590588, + -0.004462839400732194, + -0.07991768713640306, + -0.17407470231312427, + -0.18732483013668594, + -0.08463055196615278, + -0.2477115493728633, + -0.2502897149395328, + -0.20398064814312206, + -0.09077284582582087, + 0.22239547449983668, + 0.004462839400732508, + -0.22767156949803466, + 0.04275388869901973, + -0.3059839224841771, + 0.3196937439459745, + 0.006208692442055045, + 0.0001971241890499786, + -0.007422540118618717, + -0.28809669563791657, + -0.28558254792863036, + -0.3869714587097063, + 1, + 0.18322236745325193, + 0.05037142820607642, + -0.03609490261093368, + -0.07217004405828796, + -0.16005348763614577, + -0.23218078070201725, + -0.37656828808083825, + -0.29470837017550655 + ], + [ + -0.0010503798619876774, + -0.02771322371899571, + -0.30506147447521575, + -0.1453791450700764, + -0.00694955039725527, + -0.24240887436050382, + -0.26736609150996915, + -0.2739200544387702, + -0.23862820953475447, + -0.22076087097198827, + -0.2213881765552116, + -0.003859801258113621, + 0.3196275743451608, + 0.25617128287621094, + 0.006949550397255344, + -0.26333120743722876, + -0.0014704079413737543, + -0.021440560786684968, + 0.027554030004254233, + 0.49205202568054424, + -0.2512993032332009, + -0.33385014178843664, + -0.18585512574019983, + -0.18416488323856553, + 0.1605298863682866, + 0.18322236745325193, + 1, + -0.2762680073398803, + -0.3623015030946614, + -0.2451384283025972, + -0.33468123603042815, + -0.7543297340826929, + -0.19188064290675352, + -0.5924430690900127 + ], + [ + -0.0006494991207074467, + 0.0018604411671567017, + -0.048481275955609554, + -0.0014594010172321779, + 0.012306612692869211, + -0.0556619409198579, + -0.08408097138202993, + -0.07733200729381666, + -0.07201850701116608, + -0.050234026791169964, + -0.054338325985925064, + 0.003328006906514837, + 0.019928968647699895, + 0.05170274575687212, + -0.012306612692869208, + -0.04494367118136859, + -0.013428683581908657, + -0.0014940221824271288, + 0.01729491162511594, + 0.14000368638519403, + -0.017196458507178478, + -0.14674905438081498, + -0.04632871286727803, + -0.03964706284937272, + 0.030386796069204806, + 0.05037142820607642, + -0.2762680073398803, + 1, + -0.2235164922454485, + -0.15123448603095185, + -0.20647658168386077, + -0.23667330012633156, + -0.04722022100509185, + -0.21074518016919763 + ], + [ + -0.010516394125351592, + 0.02038346087056509, + 0.028467762106169584, + 0.02464494581436856, + -0.014777815951004124, + 0.0076950987178344265, + 0.023085000444599418, + 0.0449724925036831, + 0.022136746625123563, + 0.02882937792536747, + 0.02504810987080452, + -0.005388027856510724, + -0.07585881574899747, + -0.03215707580783382, + 0.014777815951004133, + 0.023690157563696343, + -0.0004678565419111244, + 0.005613761063888361, + -0.006230482005525746, + -0.05215631834144274, + 0.15389327309228798, + -0.08589992270093438, + 0.02172285552026243, + 0.028232511385228868, + -0.011569992641797876, + -0.03609490261093368, + -0.3623015030946614, + -0.2235164922454485, + 1, + -0.19833089664036088, + -0.270776108381858, + 0.08315302530583908, + 0.020378382125023714, + 0.025594453789595965 + ], + [ + -0.004318975744551275, + 0.01418568213097567, + 0.10534126166196492, + 0.03141940765317757, + -0.009750281447949313, + 0.0716632710172793, + 0.09919210454041022, + 0.07685425917962475, + 0.06270980841796413, + 0.08796930347195206, + 0.08661557153323526, + 0.010626123397145876, + -0.10079964105628228, + -0.08063023581160907, + 0.009750281447949528, + 0.07573985650934924, + -0.00079001062411148, + 0.01746632456448475, + -0.020153136101165186, + -0.19870886170688193, + 0.15891681528381144, + 0.08008211690632902, + 0.060183035947993645, + 0.048166948994686994, + -0.030584308144429393, + -0.07217004405828796, + -0.2451384283025972, + -0.15123448603095185, + -0.19833089664036088, + 1, + -0.18321102469526726, + 0.3293670640558551, + 0.07004784345008332, + 0.2529046056409522 + ], + [ + 0.01627881894278637, + -0.0024069937431968856, + 0.28035324824529767, + 0.1180897178470907, + 0.020515848357693, + 0.26322845571231857, + 0.2787875817043761, + 0.2755370112516221, + 0.26539627802277554, + 0.1981501311372027, + 0.20754366554274195, + -0.001414839543993747, + -0.2260781096915624, + -0.2427981631428492, + -0.02051584835769357, + 0.25792003827196397, + 0.014665913237832425, + 0.0061120739544805805, + -0.024291768931540957, + -0.47691223651608966, + 0.01614171428694319, + 0.5403360993105428, + 0.1842491799195877, + 0.17929367300430438, + -0.1754558932547761, + -0.16005348763614577, + -0.33468123603042815, + -0.20647658168386077, + -0.270776108381858, + -0.18321102469526726, + 1, + 0.7272367846079387, + 0.18543957041906836, + 0.6393119430257832 + ], + [ + 0.005285371870295646, + 0.01568347989913396, + 0.38191150910757077, + 0.16338596691556453, + 0.007877333295041818, + 0.32829748818662485, + 0.36113847824658735, + 0.3615199952862194, + 0.325288454100009, + 0.280263628074823, + 0.2854022671060787, + 0.004823156615386272, + -0.3540493589532626, + -0.323890766966285, + -0.007877333295041807, + 0.33239924473562554, + 0.013786269825793156, + 0.017929529906438778, + -0.037528889581619734, + -0.649345648869048, + 0.2023384083649546, + 0.5638005002286687, + 0.24382246495742915, + 0.23280034413314615, + -0.21019749610817867, + -0.23218078070201725, + -0.7543297340826929, + -0.23667330012633156, + 0.08315302530583908, + 0.3293670640558551, + 0.7272367846079387, + 1, + 0.2468617666408947, + 0.8258804609332017 + ], + [ + -0.013779327268354416, + 0.21987422950593646, + 0.09782497186892049, + -0.11234295350128225, + 0.2480330664757158, + 0.2964469592375873, + 0.4415290881871007, + 0.48260691224313995, + 0.33830139143424953, + 0.6296678921767406, + 0.6272347301103788, + 0.3519304153712528, + 0.1928582184700881, + -0.3385136039067215, + -0.2480330664757158, + 0.4909121973267493, + -0.16136793538251534, + 0.7871948529419658, + -0.7631910615169571, + 0.0589334582292538, + 0.004809615120440435, + -0.07325607300641665, + 0.04240972759459293, + 0.030054608328366407, + 0.27111737739356384, + -0.37656828808083825, + -0.19188064290675352, + -0.04722022100509185, + 0.020378382125023714, + 0.07004784345008332, + 0.18543957041906836, + 0.2468617666408947, + 1, + 0.6510648032262032 + ], + [ + 4.783950839776602e-05, + 0.10241060539532633, + 0.31907236323857324, + 0.0646532494217739, + 0.11300826095473893, + 0.41261876950713655, + 0.510100290145439, + 0.5228814865154369, + 0.4328683682410939, + 0.5157090769923935, + 0.5198665357835017, + 0.15782978286591698, + -0.1994840835675715, + -0.39676537714107735, + -0.1130082609547389, + 0.4690421356971826, + -0.052189866637419764, + 0.3607687920142481, + -0.37487836259896, + -0.4467758743246882, + 0.17056928655718714, + 0.35803561609140894, + 0.18611944938877964, + 0.1826633671546002, + -0.060436278689198375, + -0.29470837017550655, + -0.5924430690900127, + -0.21074518016919763, + 0.025594453789595965, + 0.2529046056409522, + 0.6393119430257832, + 0.8258804609332017, + 0.6510648032262032, + 1 + ] + ] + } + ], + "layout": { + "autosize": false, + "height": 720, + "margin": { + "b": 210, + "l": 210, + "r": 0, + "t": 25 + }, + "template": { + "data": { + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + } + }, + "type": "bar" + } + ], + "barpolar": [ + { + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + } + }, + "type": "barpolar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "heatmapgl": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmapgl" + } + ], + "histogram": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "histogram" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "#E5ECF6", + "showlakes": true, + "showland": true, + "subunitcolor": "white" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "#E5ECF6", + "polar": { + "angularaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "radialaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "yaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "zaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "baxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "caxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + } + } + }, + "title": { + "text": "Correlation Matrix for variables" + }, + "width": 800, + "xaxis": { + "tickfont": { + "size": 9 + } + }, + "yaxis": { + "tickfont": { + "size": 9 + } + } + } + }, + "text/html": [ + "
\n", + " \n", + " \n", + "
\n", + " \n", + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "correlation = telcom.corr()\n", + "matrix_cols = correlation.columns.tolist()\n", + "corr_array = np.array(correlation)\n", + "trace = go.Heatmap(z=corr_array,\n", + " x=matrix_cols,\n", + " y=matrix_cols,\n", + " colorscale=\"Viridis\",\n", + " colorbar=dict(title=\"Pearson Correlation coefficient\",\n", + " titleside=\"right\"\n", + " ),\n", + " )\n", + "layout = go.Layout(dict(title=\"Correlation Matrix for variables\",\n", + " autosize=False,\n", + " height=720,\n", + " width=800,\n", + " margin=dict(r=0, l=210,\n", + " t=25, b=210,\n", + " ),\n", + " yaxis=dict(tickfont=dict(size=9)),\n", + " xaxis=dict(tickfont=dict(size=9))\n", + " )\n", + " )\n", + "fig = go.Figure(data=[trace], layout=layout)\n", + "py.iplot(fig)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "_uuid": "f944336cbe67efb3422b79864d9478e2cfbdc860" + }, + "source": [ + "### 1.6 Data Preparation for Training" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.model_selection import train_test_split\n", + "from sklearn.linear_model import LogisticRegression\n", + "from sklearn.metrics import confusion_matrix,accuracy_score,classification_report\n", + "from sklearn.metrics import roc_auc_score,roc_curve,scorer\n", + "from sklearn.metrics import f1_score\n", + "import statsmodels.api as sm\n", + "from sklearn.metrics import precision_score,recall_score\n", + "from yellowbrick.classifier import DiscriminationThreshold\n", + "\n", + "# Split into a train and test set\n", + "train, test = train_test_split(telcom,test_size = .25 ,random_state = 111)\n", + " \n", + "# Seperating dependent and independent variables\n", + "cols = [i for i in telcom.columns if i not in Id_col + target_col]\n", + "training_x = train[cols]\n", + "training_y = train[target_col]\n", + "testing_x = test[cols]\n", + "testing_y = test[target_col]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.7 Training" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "from xgboost import XGBClassifier\n", + "\n", + "model = XGBClassifier(base_score=0.5, booster='gbtree', colsample_bylevel=1,\n", + " colsample_bytree=1, gamma=0, learning_rate=0.9, max_delta_step=0,\n", + " max_depth=7, min_child_weight=1, missing=None, n_estimators=100,\n", + " n_jobs=1, nthread=None, objective='binary:logistic', random_state=0,\n", + " reg_alpha=0, reg_lambda=1, scale_pos_weight=1, seed=None,\n", + " silent=True, subsample=1)\n", + "\n", + "# Train model\n", + "model.fit(training_x, training_y)\n", + "predictions = model.predict(testing_x)\n", + "probabilities = model.predict_proba(testing_x)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.8 Analysis" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "XGBClassifier(base_score=0.5, booster='gbtree', colsample_bylevel=1,\n", + " colsample_bynode=1, colsample_bytree=1, gamma=0, gpu_id=-1,\n", + " importance_type='gain', interaction_constraints=None,\n", + " learning_rate=0.9, max_delta_step=0, max_depth=7,\n", + " min_child_weight=1, missing=nan, monotone_constraints=None,\n", + " n_estimators=100, n_jobs=1, nthread=1, num_parallel_tree=1,\n", + " objective='binary:logistic', random_state=0, reg_alpha=0,\n", + " reg_lambda=1, scale_pos_weight=1, seed=0, silent=True,\n", + " subsample=1, tree_method=None, validate_parameters=False,\n", + " verbosity=None)\n", + "\n", + " Classification report : \n", + " precision recall f1-score support\n", + "\n", + " 0 0.81 0.86 0.83 1268\n", + " 1 0.56 0.47 0.51 490\n", + "\n", + " accuracy 0.75 1758\n", + " macro avg 0.69 0.67 0.67 1758\n", + "weighted avg 0.74 0.75 0.74 1758\n", + "\n", + "Accuracy Score : 0.7514220705346986\n", + "Area under curve : 0.6655250112663361 \n", + "\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/willem/.pyenv/versions/3.7.4/lib/python3.7/site-packages/plotly/tools.py:465: DeprecationWarning:\n", + "\n", + "plotly.tools.make_subplots is deprecated, please use plotly.subplots.make_subplots instead\n", + "\n" + ] + }, + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "linkText": "Export to plot.ly", + "plotlyServerURL": "https://plot.ly", + "showLink": false + }, + "data": [ + { + "colorscale": [ + [ + 0, + "rgb(0,0,255)" + ], + [ + 0.1, + "rgb(51,153,255)" + ], + [ + 0.2, + "rgb(102,204,255)" + ], + [ + 0.3, + "rgb(153,204,255)" + ], + [ + 0.4, + "rgb(204,204,255)" + ], + [ + 0.5, + "rgb(255,255,255)" + ], + [ + 0.6, + "rgb(255,204,255)" + ], + [ + 0.7, + "rgb(255,153,255)" + ], + [ + 0.8, + "rgb(255,102,204)" + ], + [ + 0.9, + "rgb(255,102,102)" + ], + [ + 1, + "rgb(255,0,0)" + ] + ], + "name": "matrix", + "showscale": false, + "type": "heatmap", + "x": [ + "Not churn", + "Churn" + ], + "xaxis": "x", + "y": [ + "Not churn", + "Churn" + ], + "yaxis": "y", + "z": [ + [ + 1090, + 178 + ], + [ + 259, + 231 + ] + ] + }, + { + "line": { + "color": "rgb(22, 96, 167)", + "width": 2 + }, + "name": "Roc : 0.6655250112663361", + "type": "scatter", + "x": [ + 0, + 0, + 0, + 0.0015772870662460567, + 0.0015772870662460567, + 0.0031545741324921135, + 0.0031545741324921135, + 0.003943217665615142, + 0.003943217665615142, + 0.003943217665615142, + 0.003943217665615142, + 0.00473186119873817, + 0.00473186119873817, + 0.005520504731861199, + 0.005520504731861199, + 0.006309148264984227, + 0.006309148264984227, + 0.007097791798107256, + 0.007097791798107256, + 0.008675078864353312, + 0.008675078864353312, + 0.00946372239747634, + 0.00946372239747634, + 0.01025236593059937, + 0.01025236593059937, + 0.011041009463722398, + 0.011041009463722398, + 0.011829652996845425, + 0.011829652996845425, + 0.013406940063091483, + 0.013406940063091483, + 0.017350157728706624, + 0.017350157728706624, + 0.018138801261829655, + 0.018138801261829655, + 0.01892744479495268, + 0.01892744479495268, + 0.01971608832807571, + 0.01971608832807571, + 0.02050473186119874, + 0.02050473186119874, + 0.021293375394321766, + 0.021293375394321766, + 0.02444794952681388, + 0.02444794952681388, + 0.025236593059936908, + 0.025236593059936908, + 0.026813880126182965, + 0.026813880126182965, + 0.027602523659305992, + 0.027602523659305992, + 0.028391167192429023, + 0.028391167192429023, + 0.02917981072555205, + 0.02917981072555205, + 0.030757097791798107, + 0.030757097791798107, + 0.031545741324921134, + 0.031545741324921134, + 0.03391167192429022, + 0.03391167192429022, + 0.03470031545741325, + 0.03470031545741325, + 0.03548895899053628, + 0.03548895899053628, + 0.03627760252365931, + 0.03627760252365931, + 0.03785488958990536, + 0.03785488958990536, + 0.04022082018927445, + 0.04022082018927445, + 0.04100946372239748, + 0.04100946372239748, + 0.0417981072555205, + 0.0417981072555205, + 0.04258675078864353, + 0.04258675078864353, + 0.04416403785488959, + 0.04416403785488959, + 0.044952681388012616, + 0.044952681388012616, + 0.04574132492113565, + 0.04574132492113565, + 0.04652996845425868, + 0.04652996845425868, + 0.050473186119873815, + 0.050473186119873815, + 0.051261829652996846, + 0.051261829652996846, + 0.05362776025236593, + 0.05362776025236593, + 0.05441640378548896, + 0.05441640378548896, + 0.055205047318611984, + 0.055205047318611984, + 0.055993690851735015, + 0.055993690851735015, + 0.056782334384858045, + 0.056782334384858045, + 0.057570977917981075, + 0.057570977917981075, + 0.0583596214511041, + 0.0583596214511041, + 0.061514195583596214, + 0.061514195583596214, + 0.062302839116719244, + 0.062302839116719244, + 0.0638801261829653, + 0.0638801261829653, + 0.06466876971608833, + 0.06466876971608833, + 0.06545741324921135, + 0.06545741324921135, + 0.06782334384858044, + 0.06782334384858044, + 0.07018927444794952, + 0.07018927444794952, + 0.07176656151419558, + 0.07176656151419558, + 0.07334384858044164, + 0.07334384858044164, + 0.07413249211356467, + 0.07413249211356467, + 0.07570977917981073, + 0.07570977917981073, + 0.07649842271293375, + 0.07649842271293375, + 0.07807570977917981, + 0.07807570977917981, + 0.08201892744479496, + 0.08201892744479496, + 0.083596214511041, + 0.083596214511041, + 0.08517350157728706, + 0.08517350157728706, + 0.0859621451104101, + 0.0859621451104101, + 0.08675078864353312, + 0.08675078864353312, + 0.08753943217665615, + 0.08753943217665615, + 0.08990536277602523, + 0.08990536277602523, + 0.09069400630914827, + 0.09069400630914827, + 0.0914826498422713, + 0.0914826498422713, + 0.09542586750788644, + 0.09542586750788644, + 0.09779179810725552, + 0.09779179810725552, + 0.09936908517350158, + 0.09936908517350158, + 0.10252365930599369, + 0.10252365930599369, + 0.10331230283911672, + 0.10331230283911672, + 0.10488958990536278, + 0.10488958990536278, + 0.10646687697160884, + 0.10646687697160884, + 0.10725552050473186, + 0.10725552050473186, + 0.10962145110410094, + 0.10962145110410094, + 0.11041009463722397, + 0.11041009463722397, + 0.11277602523659307, + 0.11277602523659307, + 0.11356466876971609, + 0.11356466876971609, + 0.11593059936908517, + 0.11593059936908517, + 0.11829652996845426, + 0.11829652996845426, + 0.12066246056782334, + 0.12066246056782334, + 0.12145110410094637, + 0.12145110410094637, + 0.1222397476340694, + 0.1222397476340694, + 0.12460567823343849, + 0.12460567823343849, + 0.12618296529968454, + 0.12618296529968454, + 0.12854889589905363, + 0.12854889589905363, + 0.12933753943217666, + 0.12933753943217666, + 0.13249211356466878, + 0.13249211356466878, + 0.13485804416403785, + 0.13485804416403785, + 0.13564668769716087, + 0.13564668769716087, + 0.13643533123028392, + 0.13643533123028392, + 0.138801261829653, + 0.138801261829653, + 0.14274447949526814, + 0.14274447949526814, + 0.1443217665615142, + 0.1443217665615142, + 0.14511041009463724, + 0.14511041009463724, + 0.14589905362776026, + 0.14589905362776026, + 0.14668769716088328, + 0.14668769716088328, + 0.14826498422712933, + 0.14826498422712933, + 0.15063091482649843, + 0.15063091482649843, + 0.1529968454258675, + 0.1529968454258675, + 0.15378548895899052, + 0.15378548895899052, + 0.1553627760252366, + 0.1553627760252366, + 0.15694006309148265, + 0.15694006309148265, + 0.15851735015772872, + 0.16009463722397477, + 0.16009463722397477, + 0.1640378548895899, + 0.1640378548895899, + 0.16561514195583596, + 0.16561514195583596, + 0.16640378548895898, + 0.16640378548895898, + 0.16876971608832808, + 0.16876971608832808, + 0.17034700315457413, + 0.17034700315457413, + 0.17113564668769715, + 0.17113564668769715, + 0.17271293375394323, + 0.17271293375394323, + 0.17429022082018927, + 0.17429022082018927, + 0.17586750788643532, + 0.17586750788643532, + 0.17665615141955837, + 0.17665615141955837, + 0.17823343848580442, + 0.17823343848580442, + 0.17902208201892744, + 0.17902208201892744, + 0.17981072555205047, + 0.1805993690851735, + 0.1829652996845426, + 0.1829652996845426, + 0.18454258675078863, + 0.18454258675078863, + 0.18533123028391169, + 0.18533123028391169, + 0.18848580441640378, + 0.18848580441640378, + 0.1892744479495268, + 0.1892744479495268, + 0.19085173501577288, + 0.19085173501577288, + 0.1916403785488959, + 0.1916403785488959, + 0.19400630914826497, + 0.19400630914826497, + 0.19479495268138802, + 0.19479495268138802, + 0.19558359621451105, + 0.19558359621451105, + 0.19873817034700317, + 0.19873817034700317, + 0.1995268138801262, + 0.1995268138801262, + 0.20189274447949526, + 0.20189274447949526, + 0.20268138801261829, + 0.20268138801261829, + 0.20347003154574134, + 0.20347003154574134, + 0.2058359621451104, + 0.2058359621451104, + 0.2082018927444795, + 0.2082018927444795, + 0.2113564668769716, + 0.2113564668769716, + 0.21293375394321767, + 0.21293375394321767, + 0.21845425867507887, + 0.21845425867507887, + 0.2200315457413249, + 0.2200315457413249, + 0.221608832807571, + 0.221608832807571, + 0.222397476340694, + 0.222397476340694, + 0.22318611987381703, + 0.22318611987381703, + 0.22555205047318613, + 0.22555205047318613, + 0.22712933753943218, + 0.22712933753943218, + 0.2279179810725552, + 0.2279179810725552, + 0.2334384858044164, + 0.2334384858044164, + 0.2358044164037855, + 0.2358044164037855, + 0.23738170347003154, + 0.23738170347003154, + 0.23817034700315456, + 0.23817034700315456, + 0.24290220820189273, + 0.24290220820189273, + 0.24369085173501578, + 0.24369085173501578, + 0.2444794952681388, + 0.2444794952681388, + 0.24526813880126183, + 0.24526813880126183, + 0.24684542586750788, + 0.24684542586750788, + 0.250788643533123, + 0.250788643533123, + 0.25236593059936907, + 0.25236593059936907, + 0.2539432176656151, + 0.2539432176656151, + 0.2547318611987382, + 0.2547318611987382, + 0.2586750788643533, + 0.2586750788643533, + 0.26025236593059936, + 0.26025236593059936, + 0.2610410094637224, + 0.2610410094637224, + 0.26498422712933756, + 0.26498422712933756, + 0.2665615141955836, + 0.2665615141955836, + 0.26735015772870663, + 0.26735015772870663, + 0.26813880126182965, + 0.26813880126182965, + 0.2689274447949527, + 0.2689274447949527, + 0.27208201892744477, + 0.27208201892744477, + 0.27602523659305994, + 0.27602523659305994, + 0.27996845425867506, + 0.27996845425867506, + 0.2823343848580442, + 0.2823343848580442, + 0.2831230283911672, + 0.2831230283911672, + 0.28785488958990535, + 0.28785488958990535, + 0.29652996845425866, + 0.29652996845425866, + 0.2996845425867508, + 0.2996845425867508, + 0.30126182965299686, + 0.30126182965299686, + 0.3020504731861199, + 0.3020504731861199, + 0.305993690851735, + 0.305993690851735, + 0.30757097791798105, + 0.30757097791798105, + 0.3194006309148265, + 0.3194006309148265, + 0.32097791798107256, + 0.32097791798107256, + 0.3225552050473186, + 0.3225552050473186, + 0.32413249211356465, + 0.32413249211356465, + 0.3249211356466877, + 0.3249211356466877, + 0.32965299684542587, + 0.32965299684542587, + 0.3312302839116719, + 0.3312302839116719, + 0.334384858044164, + 0.334384858044164, + 0.3351735015772871, + 0.3351735015772871, + 0.33753943217665616, + 0.33753943217665616, + 0.3383280757097792, + 0.3383280757097792, + 0.3501577287066246, + 0.3501577287066246, + 0.35331230283911674, + 0.35331230283911674, + 0.35646687697160884, + 0.35646687697160884, + 0.3588328075709779, + 0.3588328075709779, + 0.36198738170347006, + 0.36198738170347006, + 0.3635646687697161, + 0.3635646687697161, + 0.3659305993690852, + 0.3659305993690852, + 0.36829652996845424, + 0.36829652996845424, + 0.37302839116719244, + 0.37302839116719244, + 0.3777602523659306, + 0.3777602523659306, + 0.3824921135646688, + 0.3824921135646688, + 0.3840694006309148, + 0.3840694006309148, + 0.3864353312302839, + 0.3864353312302839, + 0.388801261829653, + 0.388801261829653, + 0.38958990536277605, + 0.38958990536277605, + 0.3943217665615142, + 0.3943217665615142, + 0.39668769716088326, + 0.39668769716088326, + 0.3998422712933754, + 0.3998422712933754, + 0.40536277602523657, + 0.40536277602523657, + 0.4061514195583596, + 0.4061514195583596, + 0.4069400630914827, + 0.4069400630914827, + 0.4235015772870662, + 0.4235015772870662, + 0.42586750788643535, + 0.42586750788643535, + 0.42665615141955837, + 0.42665615141955837, + 0.4274447949526814, + 0.4274447949526814, + 0.4282334384858044, + 0.4282334384858044, + 0.43217665615141954, + 0.43217665615141954, + 0.43454258675078866, + 0.43454258675078866, + 0.43769716088328076, + 0.43769716088328076, + 0.444006309148265, + 0.444006309148265, + 0.444794952681388, + 0.444794952681388, + 0.44558359621451105, + 0.44558359621451105, + 0.44637223974763407, + 0.44637223974763407, + 0.44873817034700314, + 0.44873817034700314, + 0.4503154574132492, + 0.4503154574132492, + 0.4518927444794953, + 0.4518927444794953, + 0.45347003154574134, + 0.45347003154574134, + 0.45425867507886436, + 0.45425867507886436, + 0.4558359621451104, + 0.4558359621451104, + 0.47003154574132494, + 0.47003154574132494, + 0.47318611987381703, + 0.47318611987381703, + 0.47870662460567825, + 0.47870662460567825, + 0.48580441640378547, + 0.48580441640378547, + 0.49290220820189273, + 0.49290220820189273, + 0.4960567823343849, + 0.4960567823343849, + 0.49842271293375395, + 0.49842271293375395, + 0.5023659305993691, + 0.5023659305993691, + 0.5063091482649842, + 0.5063091482649842, + 0.5126182965299685, + 0.5126182965299685, + 0.5134069400630915, + 0.5134069400630915, + 0.5165615141955836, + 0.5165615141955836, + 0.5205047318611987, + 0.5205047318611987, + 0.5331230283911672, + 0.5331230283911672, + 0.5528391167192429, + 0.5528391167192429, + 0.554416403785489, + 0.554416403785489, + 0.5662460567823344, + 0.5662460567823344, + 0.5717665615141956, + 0.5717665615141956, + 0.5741324921135647, + 0.5741324921135647, + 0.5757097791798107, + 0.5757097791798107, + 0.5891167192429022, + 0.5906940063091483, + 0.5954258675078864, + 0.5954258675078864, + 0.6041009463722398, + 0.6041009463722398, + 0.6072555205047319, + 0.6072555205047319, + 0.6088328075709779, + 0.6088328075709779, + 0.612776025236593, + 0.612776025236593, + 0.6151419558359621, + 0.6151419558359621, + 0.6238170347003155, + 0.6238170347003155, + 0.6246056782334385, + 0.6246056782334385, + 0.6348580441640379, + 0.6348580441640379, + 0.63801261829653, + 0.63801261829653, + 0.6482649842271293, + 0.6482649842271293, + 0.6529968454258676, + 0.6529968454258676, + 0.6648264984227129, + 0.6648264984227129, + 0.6758675078864353, + 0.6758675078864353, + 0.6892744479495269, + 0.6892744479495269, + 0.7011041009463722, + 0.7011041009463722, + 0.7089905362776026, + 0.7089905362776026, + 0.7342271293375394, + 0.7342271293375394, + 0.7570977917981072, + 0.7570977917981072, + 0.7941640378548895, + 0.7941640378548895, + 0.8138801261829653, + 0.8138801261829653, + 0.8422712933753943, + 0.8422712933753943, + 0.8698738170347003, + 0.8698738170347003, + 0.8738170347003155, + 0.8738170347003155, + 0.8832807570977917, + 0.8832807570977917, + 0.8848580441640379, + 0.8848580441640379, + 0.9069400630914827, + 0.9069400630914827, + 0.9353312302839116, + 0.9353312302839116, + 1 + ], + "xaxis": "x2", + "y": [ + 0, + 0.0020408163265306124, + 0.006122448979591836, + 0.006122448979591836, + 0.01020408163265306, + 0.01020408163265306, + 0.0163265306122449, + 0.0163265306122449, + 0.024489795918367346, + 0.02857142857142857, + 0.03877551020408163, + 0.03877551020408163, + 0.04081632653061224, + 0.04081632653061224, + 0.05714285714285714, + 0.05714285714285714, + 0.05918367346938776, + 0.05918367346938776, + 0.07142857142857142, + 0.07142857142857142, + 0.07551020408163266, + 0.07551020408163266, + 0.07755102040816327, + 0.07755102040816327, + 0.08571428571428572, + 0.08571428571428572, + 0.09183673469387756, + 0.09183673469387756, + 0.09387755102040816, + 0.09387755102040816, + 0.09591836734693877, + 0.09591836734693877, + 0.09795918367346938, + 0.09795918367346938, + 0.10408163265306122, + 0.10408163265306122, + 0.11632653061224489, + 0.11632653061224489, + 0.11836734693877551, + 0.11836734693877551, + 0.1326530612244898, + 0.1326530612244898, + 0.1346938775510204, + 0.1346938775510204, + 0.13877551020408163, + 0.13877551020408163, + 0.1489795918367347, + 0.1489795918367347, + 0.1510204081632653, + 0.1510204081632653, + 0.16938775510204082, + 0.17142857142857143, + 0.17346938775510204, + 0.17346938775510204, + 0.17959183673469387, + 0.17959183673469387, + 0.1816326530612245, + 0.1816326530612245, + 0.1836734693877551, + 0.1836734693877551, + 0.19183673469387755, + 0.19183673469387755, + 0.20408163265306123, + 0.20408163265306123, + 0.20612244897959184, + 0.20612244897959184, + 0.21224489795918366, + 0.21224489795918366, + 0.21428571428571427, + 0.21428571428571427, + 0.21836734693877552, + 0.21836734693877552, + 0.22448979591836735, + 0.22448979591836735, + 0.22857142857142856, + 0.22857142857142856, + 0.23469387755102042, + 0.23469387755102042, + 0.23877551020408164, + 0.23877551020408164, + 0.24081632653061225, + 0.24081632653061225, + 0.24897959183673468, + 0.24897959183673468, + 0.2510204081632653, + 0.2510204081632653, + 0.25918367346938775, + 0.25918367346938775, + 0.26326530612244897, + 0.26326530612244897, + 0.2857142857142857, + 0.2857142857142857, + 0.28775510204081634, + 0.28775510204081634, + 0.2897959183673469, + 0.2897959183673469, + 0.29591836734693877, + 0.29591836734693877, + 0.2979591836734694, + 0.2979591836734694, + 0.3040816326530612, + 0.3040816326530612, + 0.31020408163265306, + 0.31020408163265306, + 0.3122448979591837, + 0.3122448979591837, + 0.3142857142857143, + 0.3142857142857143, + 0.32040816326530613, + 0.32040816326530613, + 0.32653061224489793, + 0.32653061224489793, + 0.3306122448979592, + 0.3306122448979592, + 0.3326530612244898, + 0.3326530612244898, + 0.3346938775510204, + 0.3346938775510204, + 0.336734693877551, + 0.336734693877551, + 0.33877551020408164, + 0.33877551020408164, + 0.3408163265306122, + 0.3408163265306122, + 0.3469387755102041, + 0.3469387755102041, + 0.35306122448979593, + 0.35306122448979593, + 0.3551020408163265, + 0.3551020408163265, + 0.35918367346938773, + 0.35918367346938773, + 0.36122448979591837, + 0.36122448979591837, + 0.363265306122449, + 0.363265306122449, + 0.3653061224489796, + 0.3653061224489796, + 0.3693877551020408, + 0.3693877551020408, + 0.37142857142857144, + 0.37142857142857144, + 0.373469387755102, + 0.373469387755102, + 0.37551020408163266, + 0.37551020408163266, + 0.3816326530612245, + 0.3816326530612245, + 0.38571428571428573, + 0.38571428571428573, + 0.3877551020408163, + 0.3877551020408163, + 0.38979591836734695, + 0.38979591836734695, + 0.39183673469387753, + 0.39183673469387753, + 0.39387755102040817, + 0.39387755102040817, + 0.3979591836734694, + 0.3979591836734694, + 0.4, + 0.4, + 0.4020408163265306, + 0.4020408163265306, + 0.41020408163265304, + 0.41020408163265304, + 0.4122448979591837, + 0.4122448979591837, + 0.41836734693877553, + 0.41836734693877553, + 0.4204081632653061, + 0.4204081632653061, + 0.42244897959183675, + 0.42244897959183675, + 0.42448979591836733, + 0.42448979591836733, + 0.42653061224489797, + 0.42653061224489797, + 0.42857142857142855, + 0.42857142857142855, + 0.4326530612244898, + 0.4326530612244898, + 0.43673469387755104, + 0.43673469387755104, + 0.4387755102040816, + 0.4387755102040816, + 0.4448979591836735, + 0.4448979591836735, + 0.45102040816326533, + 0.45102040816326533, + 0.4530612244897959, + 0.4530612244897959, + 0.45510204081632655, + 0.45510204081632655, + 0.45714285714285713, + 0.45714285714285713, + 0.45918367346938777, + 0.45918367346938777, + 0.4714285714285714, + 0.4714285714285714, + 0.47346938775510206, + 0.47346938775510206, + 0.4816326530612245, + 0.4816326530612245, + 0.48367346938775513, + 0.48367346938775513, + 0.4857142857142857, + 0.4857142857142857, + 0.5020408163265306, + 0.5020408163265306, + 0.5040816326530613, + 0.5040816326530613, + 0.5061224489795918, + 0.5061224489795918, + 0.5102040816326531, + 0.5102040816326531, + 0.5122448979591837, + 0.5122448979591837, + 0.5224489795918368, + 0.5224489795918368, + 0.5244897959183673, + 0.5244897959183673, + 0.5244897959183673, + 0.5265306122448979, + 0.5265306122448979, + 0.5306122448979592, + 0.5306122448979592, + 0.536734693877551, + 0.536734693877551, + 0.5387755102040817, + 0.5387755102040817, + 0.5408163265306123, + 0.5408163265306123, + 0.5428571428571428, + 0.5428571428571428, + 0.5469387755102041, + 0.5469387755102041, + 0.5530612244897959, + 0.5530612244897959, + 0.5551020408163265, + 0.5551020408163265, + 0.5653061224489796, + 0.5653061224489796, + 0.5673469387755102, + 0.5673469387755102, + 0.5714285714285714, + 0.5714285714285714, + 0.573469387755102, + 0.573469387755102, + 0.5755102040816327, + 0.5755102040816327, + 0.5775510204081633, + 0.5775510204081633, + 0.5795918367346938, + 0.5795918367346938, + 0.5836734693877551, + 0.5836734693877551, + 0.5897959183673469, + 0.5897959183673469, + 0.5918367346938775, + 0.5918367346938775, + 0.5938775510204082, + 0.5938775510204082, + 0.5959183673469388, + 0.5959183673469388, + 0.5979591836734693, + 0.5979591836734693, + 0.6020408163265306, + 0.6020408163265306, + 0.6040816326530613, + 0.6040816326530613, + 0.6061224489795919, + 0.6061224489795919, + 0.6122448979591837, + 0.6122448979591837, + 0.6163265306122448, + 0.6163265306122448, + 0.6183673469387755, + 0.6183673469387755, + 0.6204081632653061, + 0.6204081632653061, + 0.6224489795918368, + 0.6224489795918368, + 0.6244897959183674, + 0.6244897959183674, + 0.6285714285714286, + 0.6285714285714286, + 0.6306122448979592, + 0.6306122448979592, + 0.6326530612244898, + 0.6326530612244898, + 0.6346938775510204, + 0.6346938775510204, + 0.636734693877551, + 0.636734693877551, + 0.6387755102040816, + 0.6387755102040816, + 0.6428571428571429, + 0.6428571428571429, + 0.6489795918367347, + 0.6489795918367347, + 0.6530612244897959, + 0.6530612244897959, + 0.6551020408163265, + 0.6551020408163265, + 0.6571428571428571, + 0.6571428571428571, + 0.6632653061224489, + 0.6632653061224489, + 0.6673469387755102, + 0.6673469387755102, + 0.6693877551020408, + 0.6693877551020408, + 0.6755102040816326, + 0.6755102040816326, + 0.6775510204081633, + 0.6775510204081633, + 0.6795918367346939, + 0.6795918367346939, + 0.6836734693877551, + 0.6836734693877551, + 0.6857142857142857, + 0.6857142857142857, + 0.6877551020408164, + 0.6877551020408164, + 0.689795918367347, + 0.689795918367347, + 0.6918367346938775, + 0.6918367346938775, + 0.6938775510204082, + 0.6938775510204082, + 0.6979591836734694, + 0.6979591836734694, + 0.7020408163265306, + 0.7020408163265306, + 0.7040816326530612, + 0.7040816326530612, + 0.7061224489795919, + 0.7061224489795919, + 0.7081632653061225, + 0.7081632653061225, + 0.710204081632653, + 0.710204081632653, + 0.7122448979591837, + 0.7122448979591837, + 0.7163265306122449, + 0.7163265306122449, + 0.7204081632653061, + 0.7204081632653061, + 0.7244897959183674, + 0.7244897959183674, + 0.7326530612244898, + 0.7326530612244898, + 0.7346938775510204, + 0.7346938775510204, + 0.736734693877551, + 0.736734693877551, + 0.7408163265306122, + 0.7408163265306122, + 0.7428571428571429, + 0.7428571428571429, + 0.7448979591836735, + 0.7448979591836735, + 0.746938775510204, + 0.746938775510204, + 0.7510204081632653, + 0.7510204081632653, + 0.7591836734693878, + 0.7591836734693878, + 0.7653061224489796, + 0.7653061224489796, + 0.7673469387755102, + 0.7673469387755102, + 0.7693877551020408, + 0.7693877551020408, + 0.773469387755102, + 0.773469387755102, + 0.7775510204081633, + 0.7775510204081633, + 0.7795918367346939, + 0.7795918367346939, + 0.7816326530612245, + 0.7816326530612245, + 0.7836734693877551, + 0.7836734693877551, + 0.7857142857142857, + 0.7857142857142857, + 0.789795918367347, + 0.789795918367347, + 0.7918367346938775, + 0.7918367346938775, + 0.7938775510204081, + 0.7938775510204081, + 0.7959183673469388, + 0.7959183673469388, + 0.7979591836734694, + 0.7979591836734694, + 0.8, + 0.8, + 0.8020408163265306, + 0.8020408163265306, + 0.8040816326530612, + 0.8040816326530612, + 0.8081632653061225, + 0.8081632653061225, + 0.810204081632653, + 0.810204081632653, + 0.8142857142857143, + 0.8142857142857143, + 0.8163265306122449, + 0.8163265306122449, + 0.8183673469387756, + 0.8183673469387756, + 0.8204081632653061, + 0.8204081632653061, + 0.8224489795918367, + 0.8224489795918367, + 0.8244897959183674, + 0.8244897959183674, + 0.826530612244898, + 0.826530612244898, + 0.8346938775510204, + 0.8346938775510204, + 0.8367346938775511, + 0.8367346938775511, + 0.8428571428571429, + 0.8428571428571429, + 0.8469387755102041, + 0.8469387755102041, + 0.8489795918367347, + 0.8489795918367347, + 0.8510204081632653, + 0.8510204081632653, + 0.8530612244897959, + 0.8530612244897959, + 0.8551020408163266, + 0.8551020408163266, + 0.8571428571428571, + 0.8571428571428571, + 0.8591836734693877, + 0.8591836734693877, + 0.8612244897959184, + 0.8612244897959184, + 0.863265306122449, + 0.863265306122449, + 0.8653061224489796, + 0.8653061224489796, + 0.8693877551020408, + 0.8693877551020408, + 0.8714285714285714, + 0.8714285714285714, + 0.8734693877551021, + 0.8734693877551021, + 0.8755102040816326, + 0.8755102040816326, + 0.8775510204081632, + 0.8775510204081632, + 0.8795918367346939, + 0.8795918367346939, + 0.8816326530612245, + 0.8816326530612245, + 0.8857142857142857, + 0.8857142857142857, + 0.8877551020408163, + 0.8877551020408163, + 0.889795918367347, + 0.889795918367347, + 0.8918367346938776, + 0.8918367346938776, + 0.8938775510204081, + 0.8938775510204081, + 0.8959183673469387, + 0.8959183673469387, + 0.9, + 0.9, + 0.9020408163265307, + 0.9020408163265307, + 0.9040816326530612, + 0.9040816326530612, + 0.9081632653061225, + 0.9081632653061225, + 0.9102040816326531, + 0.9102040816326531, + 0.9122448979591836, + 0.9122448979591836, + 0.9142857142857143, + 0.9142857142857143, + 0.9163265306122449, + 0.9163265306122449, + 0.9183673469387755, + 0.9183673469387755, + 0.9204081632653062, + 0.9204081632653062, + 0.9224489795918367, + 0.9224489795918367, + 0.9244897959183673, + 0.9244897959183673, + 0.926530612244898, + 0.926530612244898, + 0.9285714285714286, + 0.9285714285714286, + 0.9306122448979591, + 0.9306122448979591, + 0.9346938775510204, + 0.9346938775510204, + 0.936734693877551, + 0.936734693877551, + 0.9387755102040817, + 0.9387755102040817, + 0.9408163265306122, + 0.9408163265306122, + 0.9408163265306122, + 0.9408163265306122, + 0.9428571428571428, + 0.9428571428571428, + 0.9448979591836735, + 0.9448979591836735, + 0.9469387755102041, + 0.9469387755102041, + 0.9489795918367347, + 0.9489795918367347, + 0.9510204081632653, + 0.9510204081632653, + 0.9530612244897959, + 0.9530612244897959, + 0.9551020408163265, + 0.9551020408163265, + 0.9571428571428572, + 0.9571428571428572, + 0.9591836734693877, + 0.9591836734693877, + 0.963265306122449, + 0.963265306122449, + 0.9653061224489796, + 0.9653061224489796, + 0.9673469387755103, + 0.9673469387755103, + 0.9693877551020408, + 0.9693877551020408, + 0.9714285714285714, + 0.9714285714285714, + 0.9734693877551021, + 0.9734693877551021, + 0.9755102040816327, + 0.9755102040816327, + 0.9775510204081632, + 0.9775510204081632, + 0.9795918367346939, + 0.9795918367346939, + 0.9816326530612245, + 0.9816326530612245, + 0.9836734693877551, + 0.9836734693877551, + 0.9857142857142858, + 0.9857142857142858, + 0.9877551020408163, + 0.9877551020408163, + 0.9897959183673469, + 0.9897959183673469, + 0.9918367346938776, + 0.9918367346938776, + 0.9938775510204082, + 0.9938775510204082, + 0.9959183673469387, + 0.9959183673469387, + 0.9979591836734694, + 0.9979591836734694, + 1, + 1 + ], + "yaxis": "y2" + }, + { + "line": { + "color": "rgb(205, 12, 24)", + "dash": "dot", + "width": 2 + }, + "type": "scatter", + "x": [ + 0, + 1 + ], + "xaxis": "x2", + "y": [ + 0, + 1 + ], + "yaxis": "y2" + }, + { + "marker": { + "color": [ + 0.40381771326065063, + 0.28717586398124695, + 0.019390283152461052, + 0.018444739282131195, + 0.01538677979260683, + 0.014990396797657013, + 0.014325669966638088, + 0.013916175812482834, + 0.012448116205632687, + 0.01242363452911377, + 0.012042813934385777, + 0.012016979977488518, + 0.011734840460121632, + 0.011441107839345932, + 0.01139832753688097, + 0.009455341845750809, + 0.009003291837871075, + 0.008989614434540272, + 0.00892818532884121, + 0.008804365992546082, + 0.00837039016187191, + 0.008362136781215668, + 0.008307449519634247, + 0.008072943426668644, + 0.00748491520062089, + 0.007470999378710985, + 0.006756791844964027, + 0.006560647860169411, + 0.006558740511536598, + 0.006327662151306868, + 0.0055321562103927135, + 0.00406095152720809, + 0 + ], + "colorscale": [ + [ + 0, + "rgb(0,0,255)" + ], + [ + 0.1, + "rgb(51,153,255)" + ], + [ + 0.2, + "rgb(102,204,255)" + ], + [ + 0.3, + "rgb(153,204,255)" + ], + [ + 0.4, + "rgb(204,204,255)" + ], + [ + 0.5, + "rgb(255,255,255)" + ], + [ + 0.6, + "rgb(255,204,255)" + ], + [ + 0.7, + "rgb(255,153,255)" + ], + [ + 0.8, + "rgb(255,102,204)" + ], + [ + 0.9, + "rgb(255,102,102)" + ], + [ + 1, + "rgb(255,0,0)" + ] + ], + "line": { + "color": "black", + "width": 0.6 + } + }, + "name": "coefficients", + "type": "bar", + "x": [ + "internetservice_fiber_optic", + "contract_month_to_month", + "internetservice_dsl", + "tenure_group_tenure_0_12", + "phoneservice", + "contract_one_year", + "tenure", + "tenure_group_tenure_48_60", + "internetservice_no", + "seniorcitizen", + "onlinesecurity", + "techsupport", + "streamingmovies", + "multiplelines_no", + "contract_two_year", + "paymentmethod_electronic_check", + "onlinebackup", + "totalcharges", + "paymentmethod_bank_transfer_automatic", + "tenure_group_tenure_24_48", + "monthlycharges", + "paperlessbilling", + "streamingtv", + "multiplelines_yes", + "paymentmethod_mailed_check", + "gender", + "dependents", + "partner", + "tenure_group_tenure_12_24", + "deviceprotection", + "paymentmethod_credit_card_automatic", + "tenure_group_tenure_gt_60", + "multiplelines_no_phone_service" + ], + "xaxis": "x3", + "y": [ + 0.40381771326065063, + 0.28717586398124695, + 0.019390283152461052, + 0.018444739282131195, + 0.01538677979260683, + 0.014990396797657013, + 0.014325669966638088, + 0.013916175812482834, + 0.012448116205632687, + 0.01242363452911377, + 0.012042813934385777, + 0.012016979977488518, + 0.011734840460121632, + 0.011441107839345932, + 0.01139832753688097, + 0.009455341845750809, + 0.009003291837871075, + 0.008989614434540272, + 0.00892818532884121, + 0.008804365992546082, + 0.00837039016187191, + 0.008362136781215668, + 0.008307449519634247, + 0.008072943426668644, + 0.00748491520062089, + 0.007470999378710985, + 0.006756791844964027, + 0.006560647860169411, + 0.006558740511536598, + 0.006327662151306868, + 0.0055321562103927135, + 0.00406095152720809, + 0 + ], + "yaxis": "y3" + } + ], + "layout": { + "annotations": [ + { + "font": { + "size": 16 + }, + "showarrow": false, + "text": "Confusion Matrix", + "x": 0.225, + "xanchor": "center", + "xref": "paper", + "y": 1, + "yanchor": "bottom", + "yref": "paper" + }, + { + "font": { + "size": 16 + }, + "showarrow": false, + "text": "Receiver operating characteristic", + "x": 0.775, + "xanchor": "center", + "xref": "paper", + "y": 1, + "yanchor": "bottom", + "yref": "paper" + }, + { + "font": { + "size": 16 + }, + "showarrow": false, + "text": "Feature Importances", + "x": 0.5, + "xanchor": "center", + "xref": "paper", + "y": 0.375, + "yanchor": "bottom", + "yref": "paper" + } + ], + "autosize": false, + "height": 900, + "margin": { + "b": 195 + }, + "paper_bgcolor": "rgba(240,240,240, 0.95)", + "plot_bgcolor": "rgba(240,240,240, 0.95)", + "showlegend": false, + "template": { + "data": { + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + } + }, + "type": "bar" + } + ], + "barpolar": [ + { + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + } + }, + "type": "barpolar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "heatmapgl": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmapgl" + } + ], + "histogram": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "histogram" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "#E5ECF6", + "showlakes": true, + "showland": true, + "subunitcolor": "white" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "#E5ECF6", + "polar": { + "angularaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "radialaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "yaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "zaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "baxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "caxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + } + } + }, + "title": { + "text": "Model performance" + }, + "width": 800, + "xaxis": { + "anchor": "y", + "domain": [ + 0, + 0.45 + ] + }, + "xaxis2": { + "anchor": "y2", + "domain": [ + 0.55, + 1 + ], + "title": { + "text": "false positive rate" + } + }, + "xaxis3": { + "anchor": "y3", + "domain": [ + 0, + 1 + ], + "showgrid": true, + "tickangle": 90, + "tickfont": { + "size": 10 + } + }, + "yaxis": { + "anchor": "x", + "domain": [ + 0.625, + 1 + ] + }, + "yaxis2": { + "anchor": "x2", + "domain": [ + 0.625, + 1 + ], + "title": { + "text": "true positive rate" + } + }, + "yaxis3": { + "anchor": "x3", + "domain": [ + 0, + 0.375 + ] + } + } + }, + "text/html": [ + "
\n", + " \n", + " \n", + "
\n", + " \n", + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "coefficients = pd.DataFrame(model.feature_importances_)\n", + "column_df = pd.DataFrame(cols)\n", + "coef_sumry = (pd.merge(coefficients, column_df, left_index=True,\n", + " right_index=True, how=\"left\"))\n", + "coef_sumry.columns = [\"coefficients\", \"features\"]\n", + "coef_sumry = coef_sumry.sort_values(by=\"coefficients\", ascending=False)\n", + "\n", + "print(model)\n", + "print(\"\\n Classification report : \\n\", classification_report(testing_y, predictions))\n", + "print(\"Accuracy Score : \", accuracy_score(testing_y, predictions))\n", + "# confusion matrix\n", + "conf_matrix = confusion_matrix(testing_y, predictions)\n", + "# roc_auc_score\n", + "model_roc_auc = roc_auc_score(testing_y, predictions)\n", + "print(\"Area under curve : \", model_roc_auc, \"\\n\")\n", + "fpr, tpr, thresholds = roc_curve(testing_y, probabilities[:, 1])\n", + "\n", + "# plot confusion matrix\n", + "trace1 = go.Heatmap(z=conf_matrix,\n", + " x=[\"Not churn\", \"Churn\"],\n", + " y=[\"Not churn\", \"Churn\"],\n", + " showscale=False, colorscale=\"Picnic\",\n", + " name=\"matrix\")\n", + "\n", + "# plot roc curve\n", + "trace2 = go.Scatter(x=fpr, y=tpr,\n", + " name=\"Roc : \" + str(model_roc_auc),\n", + " line=dict(color=('rgb(22, 96, 167)'), width=2))\n", + "trace3 = go.Scatter(x=[0, 1], y=[0, 1],\n", + " line=dict(color=('rgb(205, 12, 24)'), width=2,\n", + " dash='dot'))\n", + "\n", + "# plot coeffs\n", + "trace4 = go.Bar(x=coef_sumry[\"features\"], y=coef_sumry[\"coefficients\"],\n", + " name=\"coefficients\",\n", + " marker=dict(color=coef_sumry[\"coefficients\"],\n", + " colorscale=\"Picnic\",\n", + " line=dict(width=.6, color=\"black\")))\n", + "\n", + "# subplots\n", + "fig = tls.make_subplots(rows=2, cols=2, specs=[[{}, {}], [{'colspan': 2}, None]],\n", + " subplot_titles=('Confusion Matrix',\n", + " 'Receiver operating characteristic',\n", + " 'Feature Importances'))\n", + "\n", + "fig.append_trace(trace1, 1, 1)\n", + "fig.append_trace(trace2, 1, 2)\n", + "fig.append_trace(trace3, 1, 2)\n", + "fig.append_trace(trace4, 2, 1)\n", + "\n", + "fig['layout'].update(showlegend=False, title=\"Model performance\",\n", + " autosize=False, height=900, width=800,\n", + " plot_bgcolor='rgba(240,240,240, 0.95)',\n", + " paper_bgcolor='rgba(240,240,240, 0.95)',\n", + " margin=dict(b=195))\n", + "fig[\"layout\"][\"xaxis2\"].update(dict(title=\"false positive rate\"))\n", + "fig[\"layout\"][\"yaxis2\"].update(dict(title=\"true positive rate\"))\n", + "fig[\"layout\"][\"xaxis3\"].update(dict(showgrid=True, tickfont=dict(size=10),\n", + " tickangle=90))\n", + "py.iplot(fig)\n", + "\n", + "visualizer = DiscriminationThreshold(model)\n", + "visualizer.fit(training_x, training_y)\n", + "visualizer.poof()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Churn Modelling (with Feast)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "1. Make sure your features are registered with Feast and that data is being published to stores\n", + "2. Train your model\n", + "3. Serve your model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.1 Configure Feast" + ] + }, + { + "cell_type": "code", + "execution_count": 540, + "metadata": {}, + "outputs": [], + "source": [ + "os.environ['FEAST_CORE_URL'] = 'localhost:6565'\n", + "os.environ['FEAST_ONLINE_URL'] = 'localhost:6566'\n", + "os.environ['FEAST_BATCH_URL'] = 'localhost:6567'\n", + "os.environ['FEAST_PROJECT'] = 'default'" + ] + }, + { + "cell_type": "code", + "execution_count": 527, + "metadata": {}, + "outputs": [], + "source": [ + "from feast import Client, FeatureSet, Entity, ValueType" + ] + }, + { + "cell_type": "code", + "execution_count": 528, + "metadata": {}, + "outputs": [], + "source": [ + "client = Client(core_url=os.environ['FEAST_CORE_URL'])\n", + "client.set_project(os.environ['FEAST_PROJECT'])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.2 Create a Feature Set" + ] + }, + { + "cell_type": "code", + "execution_count": 529, + "metadata": {}, + "outputs": [], + "source": [ + "customer_churn_fs = FeatureSet('customer_churn')" + ] + }, + { + "cell_type": "code", + "execution_count": 530, + "metadata": {}, + "outputs": [], + "source": [ + "# Add a datetime column to todays date\n", + "telcom['datetime'] = pd.Series([dt.datetime.now()] * len(entity_df))" + ] + }, + { + "cell_type": "code", + "execution_count": 278, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Entity customer_id(ValueType.STRING) manually updated (replacing an existing field).\n", + "Feature gender (ValueType.INT64) added from dataframe.\n", + "Feature seniorcitizen (ValueType.INT64) added from dataframe.\n", + "Feature partner (ValueType.INT64) added from dataframe.\n", + "Feature dependents (ValueType.INT64) added from dataframe.\n", + "Feature phoneservice (ValueType.INT64) added from dataframe.\n", + "Feature onlinesecurity (ValueType.INT64) added from dataframe.\n", + "Feature onlinebackup (ValueType.INT64) added from dataframe.\n", + "Feature deviceprotection (ValueType.INT64) added from dataframe.\n", + "Feature techsupport (ValueType.INT64) added from dataframe.\n", + "Feature streamingtv (ValueType.INT64) added from dataframe.\n", + "Feature streamingmovies (ValueType.INT64) added from dataframe.\n", + "Feature paperlessbilling (ValueType.INT64) added from dataframe.\n", + "Feature churn (ValueType.INT64) added from dataframe.\n", + "Feature multiplelines_no (ValueType.INT64) added from dataframe.\n", + "Feature multiplelines_no_phone_service (ValueType.INT64) added from dataframe.\n", + "Feature multiplelines_yes (ValueType.INT64) added from dataframe.\n", + "Feature internetservice_dsl (ValueType.INT64) added from dataframe.\n", + "Feature internetservice_fiber_optic (ValueType.INT64) added from dataframe.\n", + "Feature internetservice_no (ValueType.INT64) added from dataframe.\n", + "Feature contract_month_to_month (ValueType.INT64) added from dataframe.\n", + "Feature contract_one_year (ValueType.INT64) added from dataframe.\n", + "Feature contract_two_year (ValueType.INT64) added from dataframe.\n", + "Feature paymentmethod_bank_transfer_automatic (ValueType.INT64) added from dataframe.\n", + "Feature paymentmethod_credit_card_automatic (ValueType.INT64) added from dataframe.\n", + "Feature paymentmethod_electronic_check (ValueType.INT64) added from dataframe.\n", + "Feature paymentmethod_mailed_check (ValueType.INT64) added from dataframe.\n", + "Feature tenure_group_tenure_0_12 (ValueType.INT64) added from dataframe.\n", + "Feature tenure_group_tenure_12_24 (ValueType.INT64) added from dataframe.\n", + "Feature tenure_group_tenure_24_48 (ValueType.INT64) added from dataframe.\n", + "Feature tenure_group_tenure_48_60 (ValueType.INT64) added from dataframe.\n", + "Feature tenure_group_tenure_gt_60 (ValueType.INT64) added from dataframe.\n", + "Feature tenure (ValueType.DOUBLE) added from dataframe.\n", + "Feature monthlycharges (ValueType.DOUBLE) added from dataframe.\n", + "Feature totalcharges (ValueType.DOUBLE) added from dataframe.\n", + "\n" + ] + } + ], + "source": [ + "customer_churn_fs.infer_fields_from_df(telcom, entities=[Entity(name='customer_id', dtype=ValueType.STRING)])" + ] + }, + { + "cell_type": "code", + "execution_count": 281, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Feature set updated/created: \"customer_churn:1\"\n" + ] + } + ], + "source": [ + "client.apply(customer_churn_fs)" + ] + }, + { + "cell_type": "code", + "execution_count": 285, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"spec\": {\n", + " \"name\": \"customer_churn\",\n", + " \"version\": 1,\n", + " \"entities\": [\n", + " {\n", + " \"name\": \"customer_id\",\n", + " \"valueType\": \"STRING\"\n", + " }\n", + " ],\n", + " \"features\": [\n", + " {\n", + " \"name\": \"churn\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"seniorcitizen\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"contract_month_to_month\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"streamingmovies\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"dependents\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"paymentmethod_credit_card_automatic\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"multiplelines_no_phone_service\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"techsupport\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"internetservice_no\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"tenure_group_tenure_gt_60\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"phoneservice\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"deviceprotection\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"tenure\",\n", + " \"valueType\": \"DOUBLE\"\n", + " },\n", + " {\n", + " \"name\": \"internetservice_fiber_optic\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"multiplelines_no\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"monthlycharges\",\n", + " \"valueType\": \"DOUBLE\"\n", + " },\n", + " {\n", + " \"name\": \"gender\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"internetservice_dsl\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"onlinebackup\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"paymentmethod_electronic_check\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"tenure_group_tenure_48_60\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"paymentmethod_bank_transfer_automatic\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"totalcharges\",\n", + " \"valueType\": \"DOUBLE\"\n", + " },\n", + " {\n", + " \"name\": \"paymentmethod_mailed_check\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"tenure_group_tenure_0_12\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"tenure_group_tenure_12_24\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"multiplelines_yes\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"tenure_group_tenure_24_48\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"streamingtv\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"onlinesecurity\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"contract_one_year\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"paperlessbilling\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"contract_two_year\",\n", + " \"valueType\": \"INT64\"\n", + " },\n", + " {\n", + " \"name\": \"partner\",\n", + " \"valueType\": \"INT64\"\n", + " }\n", + " ],\n", + " \"maxAge\": \"0s\",\n", + " \"source\": {\n", + " \"type\": \"KAFKA\",\n", + " \"kafkaSourceConfig\": {\n", + " \"bootstrapServers\": \"10.202.250.99:31190\",\n", + " \"topic\": \"feast\"\n", + " }\n", + " },\n", + " \"project\": \"default\"\n", + " },\n", + " \"meta\": {\n", + " \"createdTimestamp\": \"2020-03-15T07:47:52Z\",\n", + " \"status\": \"STATUS_READY\"\n", + " }\n", + "}\n" + ] + } + ], + "source": [ + "customer_churn_fs = client.get_feature_set('customer_churn')\n", + "print(client.get_feature_set('customer_churn'))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.3 Load Features Into Feast" + ] + }, + { + "cell_type": "code", + "execution_count": 286, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\r", + " 0%| | 0/7032 [00:00 with the desired value +kubectl create secret generic feast-postgresql \ + --from-literal=postgresql-password= + +# Install Feast with Online Serving and Beam DirectRunner +helm install --name myrelease feast-charts/feast \ + --set feast-core.postgresql.existingSecret=feast-postgresql \ + --set postgresql.existingSecret=feast-postgresql ``` -feast // top level feast chart -│ -├── feast-core // feast-core subchart -│ ├── postgresql // Postgresql dependency for feast-core (Feast database) -│ └── kafka // Kafka dependency for feast-core (default stream source) -│ -├── feast-serving-online // feast-serving subchart -│ └── redis // Redis dependency for installation of store together with feast-serving -│ -└── feast-serving-batch // feast-serving subchart -``` + +## Introduction +This chart install Feast deployment on a Kubernetes cluster using the [Helm](https://v2.helm.sh/docs/using_helm/#installing-helm) package manager. ## Prerequisites -- Kubernetes 1.13 or newer cluster -- Helm 2.15.2 or newer +- Kubernetes 1.12+ +- Helm 2.15+ (not tested with Helm 3) +- Persistent Volume support on the underlying infrastructure + +## Chart Requirements + +| Repository | Name | Version | +|------------|------|---------| +| | feast-core | 0.5.0-alpha.1 | +| | feast-serving | 0.5.0-alpha.1 | +| | feast-serving | 0.5.0-alpha.1 | +| | prometheus-statsd-exporter | 0.1.2 | +| https://kubernetes-charts-incubator.storage.googleapis.com/ | kafka | 0.20.8 | +| https://kubernetes-charts.storage.googleapis.com/ | grafana | 5.0.5 | +| https://kubernetes-charts.storage.googleapis.com/ | postgresql | 8.6.1 | +| https://kubernetes-charts.storage.googleapis.com/ | prometheus | 11.0.2 | +| https://kubernetes-charts.storage.googleapis.com/ | redis | 10.5.6 | -## Resources Required -The chart deploys pods that consume minimum resources as specified in the resources configuration parameter. +## Chart Values -## Installing the Chart +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| feast-batch-serving.enabled | bool | `false` | Flag to install Feast Batch Serving | +| feast-core.enabled | bool | `true` | Flag to install Feast Core | +| feast-online-serving.enabled | bool | `true` | Flag to install Feast Online Serving | +| grafana.enabled | bool | `true` | Flag to install Grafana | +| kafka.enabled | bool | `true` | Flag to install Kafka | +| postgresql.enabled | bool | `true` | Flag to install Postgresql | +| prometheus-statsd-exporter.enabled | bool | `true` | Flag to install StatsD to Prometheus Exporter | +| prometheus.enabled | bool | `true` | Flag to install Prometheus | +| redis.enabled | bool | `true` | Flag to install Redis | + +## Configuration and installation details + +The default configuration will install Feast with Online Serving. Ingestion +of features will use Beam [DirectRunner](https://beam.apache.org/documentation/runners/direct/) +that runs on the same container where Feast Core is running. -Add repository for Feast chart: ```bash -helm repo add feast-charts https://feast-charts.storage.googleapis.com -helm repo update +# Create secret for Feast database, replace accordingly +kubectl create secret generic feast-postgresql \ + --from-literal=postgresql-password= + +# Install Feast with Online Serving and Beam DirectRunner +helm install --name myrelease feast-charts/feast \ + --set feast-core.postgresql.existingSecret=feast-postgresql \ + --set postgresql.existingSecret=feast-postgresql +``` + +In order to test that the installation is successful: +```bash +helm test myrelease + +# If the installation is successful, the following should be printed +RUNNING: myrelease-feast-online-serving-test +PASSED: myrelease-feast-online-serving-test +RUNNING: myrelease-grafana-test +PASSED: myrelease-grafana-test +RUNNING: myrelease-test-topic-create-consume-produce +PASSED: myrelease-test-topic-create-consume-produce + +# Once the test completes, to check the logs +kubectl logs myrelease-feast-online-serving-test +``` + +> The test pods can be safely deleted after the test finishes. +> Check the yaml files in `templates/tests/` folder to see the processes +> the test pods execute. + +### Feast metrics + +Feast default installation includes Grafana, StatsD exporter and Prometheus. Request +metrics from Feast Core and Feast Serving, as well as ingestion statistic from +Feast Ingestion are accessible from Prometheus and Grafana dashboard. The following +show a quick example how to access the metrics. + +``` +# Forwards local port 9090 to the Prometheus server pod +kubectl port-forward svc/myrelease-prometheus-server 9090:80 +``` + +Visit http://localhost:9090 to access the Prometheus server: + +![Prometheus Server](files/img/prometheus-server.png?raw=true) + +### Enable Batch Serving + +To install Feast Batch Serving for retrieval of historical features in offline +training, access to BigQuery is required. First, create a [service account](https://cloud.google.com/iam/docs/creating-managing-service-account-keys) key that +will provide the credentials to access BigQuery. Grant the service account `editor` +role so it has write permissions to BigQuery and Cloud Storage. + +> In production, it is advised to give only the required [permissions](foo-feast-batch-serving-test) for the +> the service account, versus `editor` role which is very permissive. + +Create a Kubernetes secret for the service account JSON file: +```bash +# By default Feast expects the secret to be named "feast-gcp-service-account" +# and the JSON file to be named "credentials.json" +kubectl create secret generic feast-gcp-service-account --from-file=credentials.json +``` + +Create a new Cloud Storage bucket (if not exists) and make sure the service +account has write access to the bucket: +```bash +gsutil mb +``` + +Use the following Helm values to enable Batch Serving: +```yaml +# values-batch-serving.yaml +feast-core: + gcpServiceAccount: + enabled: true + postgresql: + existingSecret: feast-postgresql + +feast-batch-serving: + enabled: true + gcpServiceAccount: + enabled: true + application-override.yaml: + feast: + active_store: historical + stores: + - name: historical + type: BIGQUERY + config: + project_id: + dataset_id: + staging_location: gs:///feast-staging-location + initial_retry_delay_seconds: 3 + total_timeout_seconds: 21600 + subscriptions: + - name: "*" + project: "*" + version: "*" + +postgresql: + existingSecret: feast-postgresql ``` -Install Feast release with minimal features, without batch serving and persistency: +> To delete the previous release, run `helm delete --purge myrelease` +> Note this will not delete the persistent volume that has been claimed (PVC). +> In a test cluster, run `kubectl delete pvc --all` to delete all claimed PVCs. + ```bash -RELEASE_NAME=demo -helm install feast-charts/feast --name $RELEASE_NAME --version 0.3.2 -f values-demo.yaml +# Install a new release +helm install --name myrelease -f values-batch-serving.yaml feast-charts/feast + +# Wait until all pods are created and running/completed (can take about 5m) +kubectl get pods + +# Batch Serving is installed so `helm test` will also test for batch retrieval +helm test myrelease ``` -Install Feast release for typical use cases, with batch and online serving: +### Use DataflowRunner for ingestion + +Apache Beam [DirectRunner](https://beam.apache.org/documentation/runners/direct/) +is not suitable for production use case because it is not easy to scale the +number of workers and there is no convenient API to monitor and manage the +workers. Feast supports [DataflowRunner](https://beam.apache.org/documentation/runners/dataflow/) which is a managed service on Google Cloud. + +> Make sure `feast-gcp-service-account` Kubernetes secret containing the +> service account has been created and the service account has permissions +> to manage Dataflow jobs. + +Since Dataflow workers run outside the Kube cluster and they will need to interact +with Kafka brokers, Redis stores and StatsD server installed in the cluster, +these services need to be exposed for access outside the cluster by setting +`service.type: LoadBalancer`. + +In a typical use case, 5 `LoadBalancer` (internal) IP addresses are required by +Feast when running with `DataflowRunner`. In Google Cloud, these (internal) IP +addresses should be reserved first: ```bash -# To install Feast Batch serving, BigQuery and Google Cloud service account -# is required. The service account needs to have these roles: -# - bigquery.dataEditor -# - bigquery.jobUser -# -# Assuming a service account JSON file has been downloaded to /home/user/key.json, -# run the following command to create a secret in Kubernetes -# (make sure the file name is called key.json): -kubectl create secret generic feast-gcp-service-account --from-file=/home/user/key.json - -# Set these required configuration in Feast Batch Serving -STAGING_LOCATION=gs://bucket/path -PROJECT_ID=google-cloud-project-id -DATASET_ID=bigquery-dataset-id - -# Install the Helm release using default values.yaml -helm install feast-charts/feast --name feast --version 0.3.2 \ - --set feast-serving-batch."application\.yaml".feast.jobs.staging-location=$STAGING_LOCATION \ - --set feast-serving-batch."store\.yaml".bigquery_config.project_id=$PROJECT_ID \ - --set feast-serving-batch."store\.yaml".bigquery_config.dataset_id=$DATASET_ID +# Check with your network configuration which IP addresses are available for use +gcloud compute addresses create \ + feast-kafka-1 feast-kafka-2 feast-kafka-3 feast-redis feast-statsd \ + --region --subnet \ + --addresses 10.128.0.11,10.128.0.12,10.128.0.13,10.128.0.14,10.128.0.15 ``` -## Parameters - -The following table lists the configurable parameters of the Feast chart and their default values. - -| Parameter | Description | Default -| --------- | ----------- | ------- -| `feast-core.enabled` | Flag to install Feast Core | `true` -| `feast-core.postgresql.enabled` | Flag to install Postgresql as Feast database | `true` -| `feast-core.postgresql.postgresqlDatabase` | Name of the database used by Feast Core | `feast` -| `feast-core.postgresql.postgresqlUsername` | Username to authenticate to Feast database | `postgres` -| `feast-core.postgresql.postgresqlPassword` | Passsword to authenticate to Feast database | `password` -| `feast-core.kafka.enabled` | Flag to install Kafka as the default source for Feast | `true` -| `feast-core.kafka.topics[0].name` | Default topic name in Kafka| `feast` -| `feast-core.kafka.topics[0].replicationFactor` | No of replication factor for the topic| `1` -| `feast-core.kafka.topics[0].partitions` | No of partitions for the topic | `1` -| `feast-core.replicaCount` | No of pods to create | `1` -| `feast-core.image.repository` | Repository for Feast Core Docker image | `gcr.io/kf-feast/feast-core` -| `feast-core.image.tag` | Tag for Feast Core Docker image | `0.3.2` -| `feast-core.image.pullPolicy` | Image pull policy for Feast Core Docker image | `IfNotPresent` -| `feast-core.application.yaml` | Configuration for Feast Core application | Refer to this [link](charts/feast-core/values.yaml) -| `feast-core.springConfigMountPath` | Directory to mount application.yaml | `/etc/feast/feast-core` -| `feast-core.gcpServiceAccount.useExistingSecret` | Flag to use existing secret for GCP service account | `false` -| `feast-core.gcpServiceAccount.existingSecret.name` | Secret name for the service account | `feast-gcp-service-account` -| `feast-core.gcpServiceAccount.existingSecret.key` | Secret key for the service account | `key.json` -| `feast-core.gcpServiceAccount.mountPath` | Directory to mount the JSON key file | `/etc/gcloud/service-accounts` -| `feast-core.jvmOptions` | Options for the JVM | `[]` -| `feast-core.livenessProbe.enabled` | Flag to enable liveness probe | `true` -| `feast-core.livenessProbe.initialDelaySeconds` | Delay before liveness probe is initiated | `60` -| `feast-core.livenessProbe.periodSeconds` | How often to perform the probe | `10` -| `feast-core.livenessProbe.timeoutSeconds` | Timeout duration for the probe | `5` -| `feast-core.livenessProbe.successThreshold` | Minimum no of consecutive successes for the probe to be considered successful | `1` -| `feast-core.livenessProbe.failureThreshold` | Minimum no of consecutive failures for the probe to be considered failed | `5` -| `feast-core.readinessProbe.enabled` | Flag to enable readiness probe | `true` -| `feast-core.readinessProbe.initialDelaySeconds` | Delay before readiness probe is initiated | `30` -| `feast-core.readinessProbe.periodSeconds` | How often to perform the probe | `10` -| `feast-core.readinessProbe.timeoutSeconds` | Timeout duration for the probe | `10` -| `feast-core.readinessProbe.successThreshold` | Minimum no of consecutive successes for the probe to be considered successful | `1` -| `feast-core.service.type` | Kubernetes Service Type | `ClusterIP` -| `feast-core.http.port` | Kubernetes Service port for HTTP request| `80` -| `feast-core.http.targetPort` | Container port for HTTP request | `8080` -| `feast-core.grpc.port` | Kubernetes Service port for GRPC request| `6565` -| `feast-core.grpc.targetPort` | Container port for GRPC request| `6565` -| `feast-core.resources` | CPU and memory allocation for the pod | `{}` -| `feast-serving-online.enabled` | Flag to install Feast Online Serving | `true` -| `feast-serving-online.redis.enabled` | Flag to install Redis in Feast Serving | `false` -| `feast-serving-online.redis.usePassword` | Flag to use password to access Redis | `false` -| `feast-serving-online.redis.cluster.enabled` | Flag to enable Redis cluster | `false` -| `feast-serving-online.core.enabled` | Flag for Feast Serving to use Feast Core in the same Helm release | `true` -| `feast-serving-online.replicaCount` | No of pods to create | `1` -| `feast-serving-online.image.repository` | Repository for Feast Serving Docker image | `gcr.io/kf-feast/feast-serving` -| `feast-serving-online.image.tag` | Tag for Feast Serving Docker image | `0.3.2` -| `feast-serving-online.image.pullPolicy` | Image pull policy for Feast Serving Docker image | `IfNotPresent` -| `feast-serving-online.application.yaml` | Application configuration for Feast Serving | Refer to this [link](charts/feast-serving/values.yaml) -| `feast-serving-online.store.yaml` | Store configuration for Feast Serving | Refer to this [link](charts/feast-serving/values.yaml) -| `feast-serving-online.springConfigMountPath` | Directory to mount application.yaml and store.yaml | `/etc/feast/feast-serving` -| `feast-serving-online.gcpServiceAccount.useExistingSecret` | Flag to use existing secret for GCP service account | `false` -| `feast-serving-online.gcpServiceAccount.existingSecret.name` | Secret name for the service account | `feast-gcp-service-account` -| `feast-serving-online.gcpServiceAccount.existingSecret.key` | Secret key for the service account | `key.json` -| `feast-serving-online.gcpServiceAccount.mountPath` | Directory to mount the JSON key file | `/etc/gcloud/service-accounts` -| `feast-serving-online.jvmOptions` | Options for the JVM | `[]` -| `feast-serving-online.livenessProbe.enabled` | Flag to enable liveness probe | `true` -| `feast-serving-online.livenessProbe.initialDelaySeconds` | Delay before liveness probe is initiated | `60` -| `feast-serving-online.livenessProbe.periodSeconds` | How often to perform the probe | `10` -| `feast-serving-online.livenessProbe.timeoutSeconds` | Timeout duration for the probe | `5` -| `feast-serving-online.livenessProbe.successThreshold` | Minimum no of consecutive successes for the probe to be considered successful | `1` -| `feast-serving-online.livenessProbe.failureThreshold` | Minimum no of consecutive failures for the probe to be considered failed | `5` -| `feast-serving-online.readinessProbe.enabled` | Flag to enable readiness probe | `true` -| `feast-serving-online.readinessProbe.initialDelaySeconds` | Delay before readiness probe is initiated | `30` -| `feast-serving-online.readinessProbe.periodSeconds` | How often to perform the probe | `10` -| `feast-serving-online.readinessProbe.timeoutSeconds` | Timeout duration for the probe | `10` -| `feast-serving-online.readinessProbe.successThreshold` | Minimum no of consecutive successes for the probe to be considered successful | `1` -| `feast-serving-online.service.type` | Kubernetes Service Type | `ClusterIP` -| `feast-serving-online.http.port` | Kubernetes Service port for HTTP request| `80` -| `feast-serving-online.http.targetPort` | Container port for HTTP request | `8080` -| `feast-serving-online.grpc.port` | Kubernetes Service port for GRPC request| `6566` -| `feast-serving-online.grpc.targetPort` | Container port for GRPC request| `6566` -| `feast-serving-online.resources` | CPU and memory allocation for the pod | `{}` -| `feast-serving-batch.enabled` | Flag to install Feast Batch Serving | `true` -| `feast-serving-batch.redis.enabled` | Flag to install Redis in Feast Serving | `false` -| `feast-serving-batch.redis.usePassword` | Flag to use password to access Redis | `false` -| `feast-serving-batch.redis.cluster.enabled` | Flag to enable Redis cluster | `false` -| `feast-serving-batch.core.enabled` | Flag for Feast Serving to use Feast Core in the same Helm release | `true` -| `feast-serving-batch.replicaCount` | No of pods to create | `1` -| `feast-serving-batch.image.repository` | Repository for Feast Serving Docker image | `gcr.io/kf-feast/feast-serving` -| `feast-serving-batch.image.tag` | Tag for Feast Serving Docker image | `0.3.2` -| `feast-serving-batch.image.pullPolicy` | Image pull policy for Feast Serving Docker image | `IfNotPresent` -| `feast-serving-batch.application.yaml` | Application configuration for Feast Serving | Refer to this [link](charts/feast-serving/values.yaml) -| `feast-serving-batch.store.yaml` | Store configuration for Feast Serving | Refer to this [link](charts/feast-serving/values.yaml) -| `feast-serving-batch.springConfigMountPath` | Directory to mount application.yaml and store.yaml | `/etc/feast/feast-serving` -| `feast-serving-batch.gcpServiceAccount.useExistingSecret` | Flag to use existing secret for GCP service account | `false` -| `feast-serving-batch.gcpServiceAccount.existingSecret.name` | Secret name for the service account | `feast-gcp-service-account` -| `feast-serving-batch.gcpServiceAccount.existingSecret.key` | Secret key for the service account | `key.json` -| `feast-serving-batch.gcpServiceAccount.mountPath` | Directory to mount the JSON key file | `/etc/gcloud/service-accounts` -| `feast-serving-batch.jvmOptions` | Options for the JVM | `[]` -| `feast-serving-batch.livenessProbe.enabled` | Flag to enable liveness probe | `true` -| `feast-serving-batch.livenessProbe.initialDelaySeconds` | Delay before liveness probe is initiated | `60` -| `feast-serving-batch.livenessProbe.periodSeconds` | How often to perform the probe | `10` -| `feast-serving-batch.livenessProbe.timeoutSeconds` | Timeout duration for the probe | `5` -| `feast-serving-batch.livenessProbe.successThreshold` | Minimum no of consecutive successes for the probe to be considered successful | `1` -| `feast-serving-batch.livenessProbe.failureThreshold` | Minimum no of consecutive failures for the probe to be considered failed | `5` -| `feast-serving-batch.readinessProbe.enabled` | Flag to enable readiness probe | `true` -| `feast-serving-batch.readinessProbe.initialDelaySeconds` | Delay before readiness probe is initiated | `30` -| `feast-serving-batch.readinessProbe.periodSeconds` | How often to perform the probe | `10` -| `feast-serving-batch.readinessProbe.timeoutSeconds` | Timeout duration for the probe | `10` -| `feast-serving-batch.readinessProbe.successThreshold` | Minimum no of consecutive successes for the probe to be considered successful | `1` -| `feast-serving-batch.service.type` | Kubernetes Service Type | `ClusterIP` -| `feast-serving-batch.http.port` | Kubernetes Service port for HTTP request| `80` -| `feast-serving-batch.http.targetPort` | Container port for HTTP request | `8080` -| `feast-serving-batch.grpc.port` | Kubernetes Service port for GRPC request| `6566` -| `feast-serving-batch.grpc.targetPort` | Container port for GRPC request| `6566` -| `feast-serving-batch.resources` | CPU and memory allocation for the pod | `{}` \ No newline at end of file +Use the following Helm values to enable DataflowRuner (and Batch Serving), +replacing the `<*load_balancer_ip*>` tags with the ip addresses reserved above: + +```yaml +# values-dataflow-runner.yaml +feast-core: + gcpServiceAccount: + enabled: true + postgresql: + existingSecret: feast-postgresql + application-override.yaml: + feast: + stream: + options: + bootstrapServers: + jobs: + active_runner: dataflow + metrics: + host: + runners: + - name: dataflow + type: DataflowRunner + options: + project: + region: + zone: + tempLocation: + network: + subnetwork: + maxNumWorkers: 1 + autoscalingAlgorithm: THROUGHPUT_BASED + usePublicIps: false + workerMachineType: n1-standard-1 + deadLetterTableSpec: + +feast-online-serving: + application-override.yaml: + feast: + stores: + - name: online + type: REDIS + config: + host: + port: 6379 + subscriptions: + - name: "*" + project: "*" + version: "*" + +feast-batch-serving: + enabled: true + gcpServiceAccount: + enabled: true + application-override.yaml: + feast: + active_store: historical + stores: + - name: historical + type: BIGQUERY + config: + project_id: + dataset_id: + staging_location: gs:///feast-staging-location + initial_retry_delay_seconds: 3 + total_timeout_seconds: 21600 + subscriptions: + - name: "*" + project: "*" + version: "*" + +postgresql: + existingSecret: feast-postgresql + +kafka: + external: + enabled: true + type: LoadBalancer + annotations: + cloud.google.com/load-balancer-type: Internal + loadBalancerSourceRanges: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + firstListenerPort: 31090 + loadBalancerIP: + - + - + - + configurationOverrides: + "advertised.listeners": |- + EXTERNAL://${LOAD_BALANCER_IP}:31090 + "listener.security.protocol.map": |- + PLAINTEXT:PLAINTEXT,EXTERNAL:PLAINTEXT + "log.retention.hours": 1 + +redis: + master: + service: + type: LoadBalancer + loadBalancerIP: + annotations: + cloud.google.com/load-balancer-type: Internal + loadBalancerSourceRanges: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + +prometheus-statsd-exporter: + service: + type: LoadBalancer + annotations: + cloud.google.com/load-balancer-type: Internal + loadBalancerSourceRanges: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + loadBalancerIP: +``` + +```bash +# Install a new release +helm install --name myrelease -f values-dataflow-runner.yaml feast-charts/feast + +# Wait until all pods are created and running/completed (can take about 5m) +kubectl get pods + +# Test the installation +helm test myrelease +``` + +If the tests are successful, Dataflow jobs should appear in Google Cloud console +running features ingestion: https://console.cloud.google.com/dataflow + +![Dataflow Jobs](files/img/dataflow-jobs.png) + +### Production configuration + +#### Resources requests + +The `resources` field in the deployment spec is left empty in the examples. In +production these should be set according to the load each services are expected +to handle and the service level objectives (SLO). Also Feast Core and Serving +is Java application and it is [good practice](https://stackoverflow.com/a/6916718/3949303) +to set the minimum and maximum heap. This is an example reasonable value to set for Feast Serving: + +```yaml +feast-online-serving: + javaOpts: "-Xms2048m -Xmx2048m" + resources: + limits: + memory: "2048Mi" + requests: + memory: "2048Mi" + cpu: "1" +``` + +#### High availability + +Default Feast installation only configures a single instance of Redis +server. If due to network failures or out of memory error Redis is down, +Feast serving will fail to respond to requests. Soon, Feast will support +highly available Redis via [Redis cluster](https://redis.io/topics/cluster-tutorial), +sentinel or additional proxies. + +### Documentation development + +This `README.md` is generated using [helm-docs](https://github.com/norwoodj/helm-docs/). +Please run `helm-docs` to regenerate the `README.md` every time `README.md.gotmpl` +or `values.yaml` are updated. diff --git a/infra/charts/feast/README.md.gotmpl b/infra/charts/feast/README.md.gotmpl new file mode 100644 index 00000000000..69d40fbb25e --- /dev/null +++ b/infra/charts/feast/README.md.gotmpl @@ -0,0 +1,354 @@ +{{ template "chart.header" . }} + +{{ template "chart.description" . }} {{ template "chart.versionLine" . }} + +## TL;DR; + +```bash +# Add Feast Helm chart +helm repo add feast-charts https://feast-charts.storage.googleapis.com +helm repo update + +# Create secret for Feast database, replace with the desired value +kubectl create secret generic feast-postgresql \ + --from-literal=postgresql-password= + +# Install Feast with Online Serving and Beam DirectRunner +helm install --name myrelease feast-charts/feast \ + --set feast-core.postgresql.existingSecret=feast-postgresql \ + --set postgresql.existingSecret=feast-postgresql +``` + +## Introduction +This chart install Feast deployment on a Kubernetes cluster using the [Helm](https://v2.helm.sh/docs/using_helm/#installing-helm) package manager. + +## Prerequisites +- Kubernetes 1.12+ +- Helm 2.15+ (not tested with Helm 3) +- Persistent Volume support on the underlying infrastructure + +{{ template "chart.requirementsSection" . }} + +{{ template "chart.valuesSection" . }} + +## Configuration and installation details + +The default configuration will install Feast with Online Serving. Ingestion +of features will use Beam [DirectRunner](https://beam.apache.org/documentation/runners/direct/) +that runs on the same container where Feast Core is running. + +```bash +# Create secret for Feast database, replace accordingly +kubectl create secret generic feast-postgresql \ + --from-literal=postgresql-password= + +# Install Feast with Online Serving and Beam DirectRunner +helm install --name myrelease feast-charts/feast \ + --set feast-core.postgresql.existingSecret=feast-postgresql \ + --set postgresql.existingSecret=feast-postgresql +``` + +In order to test that the installation is successful: +```bash +helm test myrelease + +# If the installation is successful, the following should be printed +RUNNING: myrelease-feast-online-serving-test +PASSED: myrelease-feast-online-serving-test +RUNNING: myrelease-grafana-test +PASSED: myrelease-grafana-test +RUNNING: myrelease-test-topic-create-consume-produce +PASSED: myrelease-test-topic-create-consume-produce + +# Once the test completes, to check the logs +kubectl logs myrelease-feast-online-serving-test +``` + +> The test pods can be safely deleted after the test finishes. +> Check the yaml files in `templates/tests/` folder to see the processes +> the test pods execute. + +### Feast metrics + +Feast default installation includes Grafana, StatsD exporter and Prometheus. Request +metrics from Feast Core and Feast Serving, as well as ingestion statistic from +Feast Ingestion are accessible from Prometheus and Grafana dashboard. The following +show a quick example how to access the metrics. + +``` +# Forwards local port 9090 to the Prometheus server pod +kubectl port-forward svc/myrelease-prometheus-server 9090:80 +``` + +Visit http://localhost:9090 to access the Prometheus server: + +![Prometheus Server](files/img/prometheus-server.png?raw=true) + +### Enable Batch Serving + +To install Feast Batch Serving for retrieval of historical features in offline +training, access to BigQuery is required. First, create a [service account](https://cloud.google.com/iam/docs/creating-managing-service-account-keys) key that +will provide the credentials to access BigQuery. Grant the service account `editor` +role so it has write permissions to BigQuery and Cloud Storage. + +> In production, it is advised to give only the required [permissions](foo-feast-batch-serving-test) for the +> the service account, versus `editor` role which is very permissive. + +Create a Kubernetes secret for the service account JSON file: +```bash +# By default Feast expects the secret to be named "feast-gcp-service-account" +# and the JSON file to be named "credentials.json" +kubectl create secret generic feast-gcp-service-account --from-file=credentials.json +``` + +Create a new Cloud Storage bucket (if not exists) and make sure the service +account has write access to the bucket: +```bash +gsutil mb +``` + +Use the following Helm values to enable Batch Serving: +```yaml +# values-batch-serving.yaml +feast-core: + gcpServiceAccount: + enabled: true + postgresql: + existingSecret: feast-postgresql + +feast-batch-serving: + enabled: true + gcpServiceAccount: + enabled: true + application-override.yaml: + feast: + active_store: historical + stores: + - name: historical + type: BIGQUERY + config: + project_id: + dataset_id: + staging_location: gs:///feast-staging-location + initial_retry_delay_seconds: 3 + total_timeout_seconds: 21600 + subscriptions: + - name: "*" + project: "*" + version: "*" + +postgresql: + existingSecret: feast-postgresql +``` + +> To delete the previous release, run `helm delete --purge myrelease` +> Note this will not delete the persistent volume that has been claimed (PVC). +> In a test cluster, run `kubectl delete pvc --all` to delete all claimed PVCs. + +```bash +# Install a new release +helm install --name myrelease -f values-batch-serving.yaml feast-charts/feast + +# Wait until all pods are created and running/completed (can take about 5m) +kubectl get pods + +# Batch Serving is installed so `helm test` will also test for batch retrieval +helm test myrelease +``` + +### Use DataflowRunner for ingestion + +Apache Beam [DirectRunner](https://beam.apache.org/documentation/runners/direct/) +is not suitable for production use case because it is not easy to scale the +number of workers and there is no convenient API to monitor and manage the +workers. Feast supports [DataflowRunner](https://beam.apache.org/documentation/runners/dataflow/) which is a managed service on Google Cloud. + +> Make sure `feast-gcp-service-account` Kubernetes secret containing the +> service account has been created and the service account has permissions +> to manage Dataflow jobs. + +Since Dataflow workers run outside the Kube cluster and they will need to interact +with Kafka brokers, Redis stores and StatsD server installed in the cluster, +these services need to be exposed for access outside the cluster by setting +`service.type: LoadBalancer`. + +In a typical use case, 5 `LoadBalancer` (internal) IP addresses are required by +Feast when running with `DataflowRunner`. In Google Cloud, these (internal) IP +addresses should be reserved first: +```bash +# Check with your network configuration which IP addresses are available for use +gcloud compute addresses create \ + feast-kafka-1 feast-kafka-2 feast-kafka-3 feast-redis feast-statsd \ + --region --subnet \ + --addresses 10.128.0.11,10.128.0.12,10.128.0.13,10.128.0.14,10.128.0.15 +``` + +Use the following Helm values to enable DataflowRuner (and Batch Serving), +replacing the `<*load_balancer_ip*>` tags with the ip addresses reserved above: + +```yaml +# values-dataflow-runner.yaml +feast-core: + gcpServiceAccount: + enabled: true + postgresql: + existingSecret: feast-postgresql + application-override.yaml: + feast: + stream: + options: + bootstrapServers: + jobs: + active_runner: dataflow + metrics: + host: + runners: + - name: dataflow + type: DataflowRunner + options: + project: + region: + zone: + tempLocation: + network: + subnetwork: + maxNumWorkers: 1 + autoscalingAlgorithm: THROUGHPUT_BASED + usePublicIps: false + workerMachineType: n1-standard-1 + deadLetterTableSpec: + +feast-online-serving: + application-override.yaml: + feast: + stores: + - name: online + type: REDIS + config: + host: + port: 6379 + subscriptions: + - name: "*" + project: "*" + version: "*" + +feast-batch-serving: + enabled: true + gcpServiceAccount: + enabled: true + application-override.yaml: + feast: + active_store: historical + stores: + - name: historical + type: BIGQUERY + config: + project_id: + dataset_id: + staging_location: gs:///feast-staging-location + initial_retry_delay_seconds: 3 + total_timeout_seconds: 21600 + subscriptions: + - name: "*" + project: "*" + version: "*" + +postgresql: + existingSecret: feast-postgresql + +kafka: + external: + enabled: true + type: LoadBalancer + annotations: + cloud.google.com/load-balancer-type: Internal + loadBalancerSourceRanges: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + firstListenerPort: 31090 + loadBalancerIP: + - + - + - + configurationOverrides: + "advertised.listeners": |- + EXTERNAL://${LOAD_BALANCER_IP}:31090 + "listener.security.protocol.map": |- + PLAINTEXT:PLAINTEXT,EXTERNAL:PLAINTEXT + "log.retention.hours": 1 + +redis: + master: + service: + type: LoadBalancer + loadBalancerIP: + annotations: + cloud.google.com/load-balancer-type: Internal + loadBalancerSourceRanges: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + +prometheus-statsd-exporter: + service: + type: LoadBalancer + annotations: + cloud.google.com/load-balancer-type: Internal + loadBalancerSourceRanges: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + loadBalancerIP: +``` + +```bash +# Install a new release +helm install --name myrelease -f values-dataflow-runner.yaml feast-charts/feast + +# Wait until all pods are created and running/completed (can take about 5m) +kubectl get pods + +# Test the installation +helm test myrelease +``` + +If the tests are successful, Dataflow jobs should appear in Google Cloud console +running features ingestion: https://console.cloud.google.com/dataflow + +![Dataflow Jobs](files/img/dataflow-jobs.png) + +### Production configuration + +#### Resources requests + +The `resources` field in the deployment spec is left empty in the examples. In +production these should be set according to the load each services are expected +to handle and the service level objectives (SLO). Also Feast Core and Serving +is Java application and it is [good practice](https://stackoverflow.com/a/6916718/3949303) +to set the minimum and maximum heap. This is an example reasonable value to set for Feast Serving: + +```yaml +feast-online-serving: + javaOpts: "-Xms2048m -Xmx2048m" + resources: + limits: + memory: "2048Mi" + requests: + memory: "2048Mi" + cpu: "1" +``` + +#### High availability + +Default Feast installation only configures a single instance of Redis +server. If due to network failures or out of memory error Redis is down, +Feast serving will fail to respond to requests. Soon, Feast will support +highly available Redis via [Redis cluster](https://redis.io/topics/cluster-tutorial), +sentinel or additional proxies. + +### Documentation development + +This `README.md` is generated using [helm-docs](https://github.com/norwoodj/helm-docs/). +Please run `helm-docs` to regenerate the `README.md` every time `README.md.gotmpl` +or `values.yaml` are updated. diff --git a/infra/charts/feast/charts/feast-core/.helmignore b/infra/charts/feast/charts/feast-core/.helmignore deleted file mode 100644 index 50af0317254..00000000000 --- a/infra/charts/feast/charts/feast-core/.helmignore +++ /dev/null @@ -1,22 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ -# Common backup files -*.swp -*.bak -*.tmp -*~ -# Various IDEs -.project -.idea/ -*.tmproj -.vscode/ diff --git a/infra/charts/feast/charts/feast-core/Chart.yaml b/infra/charts/feast/charts/feast-core/Chart.yaml index efda76d3026..5b832943cfe 100644 --- a/infra/charts/feast/charts/feast-core/Chart.yaml +++ b/infra/charts/feast/charts/feast-core/Chart.yaml @@ -1,4 +1,4 @@ apiVersion: v1 -description: A Helm chart for core component of Feast +description: Feast Core registers feature specifications and manage ingestion jobs. name: feast-core -version: 0.3.2 +version: 0.5.0-alpha.1 diff --git a/infra/charts/feast/charts/feast-core/README.md b/infra/charts/feast/charts/feast-core/README.md new file mode 100644 index 00000000000..40d6bd91129 --- /dev/null +++ b/infra/charts/feast/charts/feast-core/README.md @@ -0,0 +1,70 @@ +feast-core +========== +Feast Core registers feature specifications and manage ingestion jobs. + +Current chart version is `0.5.0-alpha.1` + + + + + +## Chart Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| "application-generated.yaml".enabled | bool | `true` | Flag to include Helm generated configuration for Feast database URL, Kafka bootstrap servers and jobs metrics host. This is useful for deployment that uses default configuration for Kafka, Postgres and StatsD exporter. Please set `application-override.yaml` to override this configuration. | +| "application-override.yaml" | object | `{"enabled":true}` | Configuration to override the default [application.yaml](https://github.com/feast-dev/feast/blob/master/core/src/main/resources/application.yml). Will be created as a ConfigMap. `application-override.yaml` has a higher precedence than `application-secret.yaml` | +| "application-secret.yaml" | object | `{"enabled":true}` | Configuration to override the default [application.yaml](https://github.com/feast-dev/feast/blob/master/core/src/main/resources/application.yml). Will be created as a Secret. `application-override.yaml` has a higher precedence than `application-secret.yaml`. It is recommended to either set `application-override.yaml` or `application-secret.yaml` only to simplify config management. | +| "application.yaml".enabled | bool | `true` | Flag to include the default [configuration](https://github.com/feast-dev/feast/blob/master/core/src/main/resources/application.yml). Please set `application-override.yaml` to override this configuration. | +| envOverrides | object | `{}` | Extra environment variables to set | +| gcpProjectId | string | `""` | Project ID to use when using Google Cloud services such as BigQuery, Cloud Storage and Dataflow | +| gcpServiceAccount.enabled | bool | `false` | Flag to use [service account](https://cloud.google.com/iam/docs/creating-managing-service-account-keys) JSON key | +| gcpServiceAccount.existingSecret.key | string | `"credentials.json"` | Key in the secret data (file name of the service account) | +| gcpServiceAccount.existingSecret.name | string | `"feast-gcp-service-account"` | Name of the existing secret containing the service account | +| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy | +| image.repository | string | `"gcr.io/kf-feast/feast-core"` | Docker image repository | +| image.tag | string | `"dev"` | Image tag | +| ingress.grpc.annotations | object | `{}` | Extra annotations for the ingress | +| ingress.grpc.auth.enabled | bool | `false` | Flag to enable auth | +| ingress.grpc.class | string | `"nginx"` | Which ingress controller to use | +| ingress.grpc.enabled | bool | `false` | Flag to create an ingress resource for the service | +| ingress.grpc.hosts | list | `[]` | List of hostnames to match when routing requests | +| ingress.grpc.https.enabled | bool | `true` | Flag to enable HTTPS | +| ingress.grpc.https.secretNames | object | `{}` | Map of hostname to TLS secret name | +| ingress.grpc.whitelist | string | `""` | Allowed client IP source ranges | +| ingress.http.annotations | object | `{}` | Extra annotations for the ingress | +| ingress.http.auth.authUrl | string | `"http://auth-server.auth-ns.svc.cluster.local/auth"` | URL to an existing authentication service | +| ingress.http.auth.enabled | bool | `false` | Flag to enable auth | +| ingress.http.class | string | `"nginx"` | Which ingress controller to use | +| ingress.http.enabled | bool | `false` | Flag to create an ingress resource for the service | +| ingress.http.hosts | list | `[]` | List of hostnames to match when routing requests | +| ingress.http.https.enabled | bool | `true` | Flag to enable HTTPS | +| ingress.http.https.secretNames | object | `{}` | Map of hostname to TLS secret name | +| ingress.http.whitelist | string | `""` | Allowed client IP source ranges | +| javaOpts | string | `nil` | [JVM options](https://docs.oracle.com/cd/E22289_01/html/821-1274/configuring-the-default-jvm-and-java-arguments.html). For better performance, it is advised to set the min and max heap:
`-Xms2048m -Xmx2048m` | +| livenessProbe.enabled | bool | `true` | Flag to enabled the probe | +| livenessProbe.failureThreshold | int | `5` | Min consecutive failures for the probe to be considered failed | +| livenessProbe.initialDelaySeconds | int | `60` | Delay before the probe is initiated | +| livenessProbe.periodSeconds | int | `10` | How often to perform the probe | +| livenessProbe.successThreshold | int | `1` | Min consecutive success for the probe to be considered successful | +| livenessProbe.timeoutSeconds | int | `5` | When the probe times out | +| logLevel | string | `"WARN"` | Default log level, use either one of `DEBUG`, `INFO`, `WARN` or `ERROR` | +| logType | string | `"Console"` | Log format, either `JSON` or `Console` | +| nodeSelector | object | `{}` | Node labels for pod assignment | +| postgresql.existingSecret | string | `""` | Existing secret to use for authenticating to Postgres | +| prometheus.enabled | bool | `true` | Flag to enable scraping of Feast Core metrics | +| readinessProbe.enabled | bool | `true` | Flag to enabled the probe | +| readinessProbe.failureThreshold | int | `5` | Min consecutive failures for the probe to be considered failed | +| readinessProbe.initialDelaySeconds | int | `20` | Delay before the probe is initiated | +| readinessProbe.periodSeconds | int | `10` | How often to perform the probe | +| readinessProbe.successThreshold | int | `1` | Min consecutive success for the probe to be considered successful | +| readinessProbe.timeoutSeconds | int | `10` | When the probe times out | +| replicaCount | int | `1` | Number of pods that will be created | +| resources | object | `{}` | CPU/memory [resource requests/limit](https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#resource-requests-and-limits-of-pod-and-container) | +| service.grpc.nodePort | string | `nil` | Port number that each cluster node will listen to | +| service.grpc.port | int | `6565` | Service port for GRPC requests | +| service.grpc.targetPort | int | `6565` | Container port serving GRPC requests | +| service.http.nodePort | string | `nil` | Port number that each cluster node will listen to | +| service.http.port | int | `80` | Service port for HTTP requests | +| service.http.targetPort | int | `8080` | Container port serving HTTP requests and Prometheus metrics | +| service.type | string | `"ClusterIP"` | Kubernetes service type | diff --git a/infra/charts/feast/charts/feast-core/charts/kafka-0.20.1.tgz b/infra/charts/feast/charts/feast-core/charts/kafka-0.20.1.tgz deleted file mode 100644 index 76a2247577d..00000000000 Binary files a/infra/charts/feast/charts/feast-core/charts/kafka-0.20.1.tgz and /dev/null differ diff --git a/infra/charts/feast/charts/feast-core/charts/postgresql-6.5.5.tgz b/infra/charts/feast/charts/feast-core/charts/postgresql-6.5.5.tgz deleted file mode 100644 index f61421514d5..00000000000 Binary files a/infra/charts/feast/charts/feast-core/charts/postgresql-6.5.5.tgz and /dev/null differ diff --git a/infra/charts/feast/charts/feast-core/requirements.yaml b/infra/charts/feast/charts/feast-core/requirements.yaml deleted file mode 100644 index efe9fec508a..00000000000 --- a/infra/charts/feast/charts/feast-core/requirements.yaml +++ /dev/null @@ -1,9 +0,0 @@ -dependencies: -- name: postgresql - version: 6.5.5 - repository: "@stable" - condition: postgresql.enabled -- name: kafka - version: 0.20.1 - repository: "@incubator" - condition: kafka.enabled \ No newline at end of file diff --git a/infra/charts/feast/charts/feast-core/templates/_ingress.yaml b/infra/charts/feast/charts/feast-core/templates/_ingress.yaml new file mode 100644 index 00000000000..5bed6df0470 --- /dev/null +++ b/infra/charts/feast/charts/feast-core/templates/_ingress.yaml @@ -0,0 +1,68 @@ +{{- /* +This takes an array of three values: +- the top context +- the feast component +- the service protocol +- the ingress context +*/ -}} +{{- define "feast.ingress" -}} +{{- $top := (index . 0) -}} +{{- $component := (index . 1) -}} +{{- $protocol := (index . 2) -}} +{{- $ingressValues := (index . 3) -}} +apiVersion: extensions/v1beta1 +kind: Ingress +{{ include "feast.ingress.metadata" . }} +spec: + rules: + {{- range $host := $ingressValues.hosts }} + - host: {{ $host }} + http: + paths: + - path: / + backend: + serviceName: {{ include (printf "feast-%s.fullname" $component) $top }} + servicePort: {{ index $top.Values "service" $protocol "port" }} + {{- end }} +{{- if $ingressValues.https.enabled }} + tls: + {{- range $host := $ingressValues.hosts }} + - secretName: {{ index $ingressValues.https.secretNames $host | default (splitList "." $host | rest | join "-" | printf "%s-tls") }} + hosts: + - {{ $host }} + {{- end }} +{{- end -}} +{{- end -}} + +{{- define "feast.ingress.metadata" -}} +{{- $commonMetadata := fromYaml (include "common.metadata" (first .)) }} +{{- $overrides := fromYaml (include "feast.ingress.metadata-overrides" .) -}} +{{- toYaml (merge $overrides $commonMetadata) -}} +{{- end -}} + +{{- define "feast.ingress.metadata-overrides" -}} +{{- $top := (index . 0) -}} +{{- $component := (index . 1) -}} +{{- $protocol := (index . 2) -}} +{{- $ingressValues := (index . 3) -}} +{{- $commonFullname := include "common.fullname" $top }} +metadata: + name: {{ $commonFullname }}-{{ $component }}-{{ $protocol }} + annotations: + kubernetes.io/ingress.class: {{ $ingressValues.class | quote }} + {{- if (and (eq $ingressValues.class "nginx") $ingressValues.auth.enabled) }} + nginx.ingress.kubernetes.io/auth-url: {{ $ingressValues.auth.authUrl | quote }} + nginx.ingress.kubernetes.io/auth-response-headers: "x-auth-request-email, x-auth-request-user" + nginx.ingress.kubernetes.io/auth-signin: "https://{{ $ingressValues.auth.signinHost | default (splitList "." (index $ingressValues.hosts 0) | rest | join "." | printf "auth.%s")}}/oauth2/start?rd=/r/$host/$request_uri" + {{- end }} + {{- if (and (eq $ingressValues.class "nginx") $ingressValues.whitelist) }} + nginx.ingress.kubernetes.io/whitelist-source-range: {{ $ingressValues.whitelist | quote -}} + {{- end }} + {{- if (and (eq $ingressValues.class "nginx") (eq $protocol "grpc") ) }} + # TODO: Allow choice of GRPC/GRPCS + nginx.ingress.kubernetes.io/backend-protocol: "GRPC" + {{- end }} + {{- if $ingressValues.annotations -}} + {{ include "common.annote" $ingressValues.annotations | indent 4 }} + {{- end }} +{{- end -}} diff --git a/infra/charts/feast/charts/feast-core/templates/configmap.yaml b/infra/charts/feast/charts/feast-core/templates/configmap.yaml index 68dc45c0571..b48e15cc985 100644 --- a/infra/charts/feast/charts/feast-core/templates/configmap.yaml +++ b/infra/charts/feast/charts/feast-core/templates/configmap.yaml @@ -10,23 +10,29 @@ metadata: release: {{ .Release.Name }} heritage: {{ .Release.Service }} data: - application.yaml: | -{{- $config := index .Values "application.yaml"}} + application-generated.yaml: | +{{- if index .Values "application-generated.yaml" "enabled" }} + spring: + datasource: + url: jdbc:postgresql://{{ .Release.Name }}-postgresql:5432/postgres + feast: + stream: + type: kafka + options: + bootstrapServers: {{ .Release.Name }}-kafka:9092 + topic: feast + jobs: + metrics: + enabled: true + type: statsd + host: {{ .Release.Name }}-prometheus-statsd-exporter-udp + port: 9125 -{{- if .Values.postgresql.enabled }} -{{- $datasource := dict "url" (printf "jdbc:postgresql://%s:%s/%s" (printf "%s-postgresql" .Release.Name) (.Values.postgresql.service.port | toString) (.Values.postgresql.postgresqlDatabase)) "driverClassName" "org.postgresql.Driver" }} -{{- $newConfig := dict "spring" (dict "datasource" $datasource) }} -{{- $config := mergeOverwrite $config $newConfig }} + server: + port: {{ .Values.service.http.targetPort }} {{- end }} -{{- if .Values.kafka.enabled }} -{{- $topic := index .Values.kafka.topics 0 }} -{{- $options := dict "topic" $topic.name "replicationFactor" $topic.replicationFactor "partitions" $topic.partitions }} -{{- if not .Values.kafka.external.enabled }} -{{- $_ := set $options "bootstrapServers" (printf "%s:9092" (printf "%s-kafka" .Release.Name)) }} -{{- end }} -{{- $newConfig := dict "feast" (dict "stream" (dict "type" "kafka" "options" $options))}} -{{- $config := mergeOverwrite $config $newConfig }} -{{- end }} - -{{- toYaml $config | nindent 4 }} + application-override.yaml: | +{{- if index .Values "application-override.yaml" "enabled" }} +{{- toYaml (index .Values "application-override.yaml") | nindent 4 }} +{{- end }} \ No newline at end of file diff --git a/infra/charts/feast/charts/feast-core/templates/deployment.yaml b/infra/charts/feast/charts/feast-core/templates/deployment.yaml index 02a533c2637..179e3a6a094 100644 --- a/infra/charts/feast/charts/feast-core/templates/deployment.yaml +++ b/infra/charts/feast/charts/feast-core/templates/deployment.yaml @@ -18,6 +18,14 @@ spec: release: {{ .Release.Name }} template: metadata: + annotations: + checksum/configmap: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + {{- if .Values.prometheus.enabled }} + prometheus.io/path: /metrics + prometheus.io/port: "{{ .Values.service.http.targetPort }}" + prometheus.io/scrape: "true" + {{- end }} labels: app: {{ template "feast-core.name" . }} component: core @@ -32,59 +40,93 @@ spec: - name: {{ template "feast-core.fullname" . }}-config configMap: name: {{ template "feast-core.fullname" . }} - {{- if .Values.gcpServiceAccount.useExistingSecret }} - - name: {{ template "feast-core.fullname" . }}-gcpserviceaccount + - name: {{ template "feast-core.fullname" . }}-secret + secret: + secretName: {{ template "feast-core.fullname" . }} + {{- if .Values.gcpServiceAccount.enabled }} + - name: {{ template "feast-core.fullname" . }}-gcp-service-account secret: secretName: {{ .Values.gcpServiceAccount.existingSecret.name }} {{- end }} containers: - name: {{ .Chart.Name }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + image: {{ .Values.image.repository }}:{{ .Values.image.tag }} imagePullPolicy: {{ .Values.image.pullPolicy }} - + volumeMounts: - name: {{ template "feast-core.fullname" . }}-config - mountPath: "{{ .Values.springConfigMountPath }}" - {{- if .Values.gcpServiceAccount.useExistingSecret }} - - name: {{ template "feast-core.fullname" . }}-gcpserviceaccount - mountPath: {{ .Values.gcpServiceAccount.mountPath }} + mountPath: /etc/feast + - name: {{ template "feast-core.fullname" . }}-secret + mountPath: /etc/secrets/feast + readOnly: true + {{- if .Values.gcpServiceAccount.enabled }} + - name: {{ template "feast-core.fullname" . }}-gcp-service-account + mountPath: /etc/secrets/google readOnly: true {{- end }} env: - {{- if .Values.postgresql.enabled }} - - name: SPRING_DATASOURCE_USERNAME - value: {{ .Values.postgresql.postgresqlUsername }} + - name: LOG_TYPE + value: {{ .Values.logType | quote }} + - name: LOG_LEVEL + value: {{ .Values.logLevel | quote }} + + {{- if .Values.postgresql.existingSecret }} - name: SPRING_DATASOURCE_PASSWORD - value: {{ .Values.postgresql.postgresqlPassword }} + valueFrom: + secretKeyRef: + name: {{ .Values.postgresql.existingSecret }} + key: postgresql-password {{- end }} - {{- if .Values.gcpServiceAccount.useExistingSecret }} + {{- if .Values.gcpServiceAccount.enabled }} - name: GOOGLE_APPLICATION_CREDENTIALS - value: {{ .Values.gcpServiceAccount.mountPath }}/{{ .Values.gcpServiceAccount.existingSecret.key }} + value: /etc/secrets/google/{{ .Values.gcpServiceAccount.existingSecret.key }} {{- end }} - command: - - java - {{- range .Values.jvmOptions }} - - {{ . }} + {{- if .Values.gcpProjectId }} + - name: GOOGLE_CLOUD_PROJECT + value: {{ .Values.gcpProjectId | quote }} + {{- end }} + + {{- if .Values.javaOpts }} + - name: JAVA_TOOL_OPTIONS + value: {{ .Values.javaOpts }} {{- end }} + + {{- range $key, $value := .Values.envOverrides }} + - name: {{ printf "%s" $key | replace "." "_" | upper | quote }} + value: {{ $value | quote }} + {{- end }} + + command: + - java - -jar - /opt/feast/feast-core.jar - - "--spring.config.location=file:{{ .Values.springConfigMountPath }}/application.yaml" - + - --spring.config.location= + {{- if index .Values "application.yaml" "enabled" -}} + classpath:/application.yml + {{- end }} + {{- if index .Values "application-generated.yaml" "enabled" -}} + ,file:/etc/feast/application-generated.yaml + {{- end }} + {{- if index .Values "application-secret.yaml" "enabled" -}} + ,file:/etc/secrets/feast/application-secret.yaml + {{- end }} + {{- if index .Values "application-override.yaml" "enabled" -}} + ,file:/etc/feast/application-override.yaml + {{- end }} ports: - name: http containerPort: {{ .Values.service.http.targetPort }} - - name: grpc + - name: grpc containerPort: {{ .Values.service.grpc.targetPort }} {{- if .Values.livenessProbe.enabled }} livenessProbe: - httpGet: - path: /healthz - port: {{ .Values.service.http.targetPort }} + exec: + command: ["/usr/bin/grpc-health-probe", "-addr=:{{ .Values.service.grpc.targetPort }}"] initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.livenessProbe.periodSeconds }} successThreshold: {{ .Values.livenessProbe.successThreshold }} @@ -94,15 +136,14 @@ spec: {{- if .Values.readinessProbe.enabled }} readinessProbe: - httpGet: - path: /healthz - port: {{ .Values.service.http.targetPort }} + exec: + command: ["/usr/bin/grpc-health-probe", "-addr=:{{ .Values.service.grpc.targetPort }}"] initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.readinessProbe.periodSeconds }} successThreshold: {{ .Values.readinessProbe.successThreshold }} timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} failureThreshold: {{ .Values.readinessProbe.failureThreshold }} {{- end }} - + resources: {{- toYaml .Values.resources | nindent 10 }} diff --git a/infra/charts/feast/charts/feast-core/templates/ingress.yaml b/infra/charts/feast/charts/feast-core/templates/ingress.yaml index 86fc2d3f175..7f453e1a75f 100644 --- a/infra/charts/feast/charts/feast-core/templates/ingress.yaml +++ b/infra/charts/feast/charts/feast-core/templates/ingress.yaml @@ -1,28 +1,7 @@ -{{- if .Values.ingress.enabled -}} -{{- $fullName := include "feast-core.fullname" . -}} -apiVersion: extensions/v1beta1 -kind: Ingress -metadata: - name: {{ $fullName }} - labels: - app: {{ template "feast-core.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version }} - component: core - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - annotations: -{{- with .Values.ingress.annotations }} -{{ toYaml . | indent 4 }} +{{- if .Values.ingress.http.enabled -}} +{{ template "feast.ingress" (list . "core" "http" .Values.ingress.http) }} +{{- end }} +--- +{{ if .Values.ingress.grpc.enabled -}} +{{ template "feast.ingress" (list . "core" "grpc" .Values.ingress.grpc) }} {{- end }} -spec: - rules: - {{- range .Values.ingress.hosts }} - - host: {{ .host | quote }} - http: - paths: - - path: / - backend: - serviceName: {{ $fullName }} - servicePort: {{ .port | quote }} - {{- end }} -{{- end }} \ No newline at end of file diff --git a/infra/charts/feast/charts/feast-core/templates/secret.yaml b/infra/charts/feast/charts/feast-core/templates/secret.yaml new file mode 100644 index 00000000000..dd33e2dd487 --- /dev/null +++ b/infra/charts/feast/charts/feast-core/templates/secret.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "feast-core.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "feast-core.name" . }} + component: core + chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +type: Opaque +stringData: + application-secret.yaml: | +{{- toYaml (index .Values "application-secret.yaml") | nindent 4 }} diff --git a/infra/charts/feast/charts/feast-core/values.yaml b/infra/charts/feast/charts/feast-core/values.yaml index 321d71c844d..f56897d2852 100644 --- a/infra/charts/feast/charts/feast-core/values.yaml +++ b/infra/charts/feast/charts/feast-core/values.yaml @@ -1,190 +1,153 @@ -# postgresql configures Postgresql that is installed as part of Feast Core. -# Refer to https://github.com/helm/charts/tree/c42002a21abf8eff839ff1d2382152bde2bbe596/stable/postgresql -# for additional configuration. -postgresql: - # enabled specifies whether Postgresql should be installed as part of Feast Core. - # - # Feast Core requires a database to store data such as the created FeatureSets - # and job statuses. If enabled, the database and service port specified below - # will override "spring.datasource.url" value in application.yaml. The - # username and password will also be set as environment variables that will - # override "spring.datasource.username/password" in application.yaml. - enabled: true - # postgresqlDatabase is the name of the database used by Feast Core. - postgresqlDatabase: feast - # postgresqlUsername is the username to authenticate to the database. - postgresqlUsername: postgres - # postgresqlPassword is the password to authenticate to the database. - postgresqlPassword: password - service: - # port is the TCP port that Postgresql will listen to - port: 5432 - -# kafka configures Kafka that is installed as part of Feast Core. -# Refer to https://github.com/helm/charts/tree/c42002a21abf8eff839ff1d2382152bde2bbe596/incubator/kafka -# for additional configuration. -kafka: - # enabled specifies whether Kafka should be installed as part of Feast Core. - # - # Feast Core requires a Kafka instance to be set as the default source for - # FeatureRows. If enabled, "feast.stream" option in application.yaml will - # be overridden by this installed Kafka configuration. - enabled: true - topics: - # topic that will be used as default in Feast Core for the default Kafka source. - - name: feast - replicationFactor: 1 - partitions: 1 - -# replicaCount is the number of pods that will be created. +# replicaCount -- Number of pods that will be created replicaCount: 1 -# image configures the Docker image for Feast Core image: + # image.repository -- Docker image repository repository: gcr.io/kf-feast/feast-core - tag: 0.3.2 + # image.tag -- Image tag + tag: dev + # image.pullPolicy -- Image pull policy pullPolicy: IfNotPresent -# application.yaml is the main configuration for Feast Core application. -# -# Feast Core is a Spring Boot app which uses this yaml configuration file. -# Refer to https://github.com/gojek/feast/blob/79eb4ab5fa3d37102c1dca9968162a98690526ba/core/src/main/resources/application.yml -# for a complete list and description of the configuration. -# -# Note that some properties defined in application.yaml may be overriden by -# Helm under certain conditions. For example, if postgresql and kafka dependencies -# are enabled. application.yaml: - grpc: - port: 6565 - enable-reflection: true - feast: - jobs: - runner: DirectRunner - options: {} - updates: - timeoutSeconds: 240 - metrics: - enabled: false - type: statsd - host: localhost - port: 9125 - stream: - type: kafka - options: - topic: TOPIC - bootstrapServers: HOST:PORT - replicationFactor: 1 - partitions: 1 - spring: - jpa: - properties.hibernate.format_sql: true - properties.hibernate.event.merge.entity_copy_observer: allow - hibernate.naming.physical-strategy=org.hibernate.boot.model.naming: PhysicalNamingStrategyStandardImpl - hibernate.ddl-auto: update - datasource: - driverClassName: org.postgresql.Driver - url: jdbc:postgresql://HOST:PORT/DATABASE - username: USERNAME - password: PASSWORD - management: - metrics: - export: - simple: - enabled: false - statsd: - enabled: false - host: localhost - port: 8125 - -# springConfigMountPath is the directory path where application.yaml will be -# mounted in the container. -springConfigMountPath: /etc/feast/feast-core - -# gcpServiceAccount is the service account that Feast Core will use. + # "application.yaml".enabled -- Flag to include the default [configuration](https://github.com/feast-dev/feast/blob/master/core/src/main/resources/application.yml). Please set `application-override.yaml` to override this configuration. + enabled: true + +application-generated.yaml: + # "application-generated.yaml".enabled -- Flag to include Helm generated configuration for http port, Feast database URL, Kafka bootstrap servers and jobs metrics host. This is useful for deployment that uses default configuration for Kafka, Postgres and StatsD exporter. Please set `application-override.yaml` to override this configuration. + enabled: true + +# "application-secret.yaml" -- Configuration to override the default [application.yaml](https://github.com/feast-dev/feast/blob/master/core/src/main/resources/application.yml). Will be created as a Secret. `application-override.yaml` has a higher precedence than `application-secret.yaml`. It is recommended to either set `application-override.yaml` or `application-secret.yaml` only to simplify config management. +application-secret.yaml: + enabled: true + +# "application-override.yaml" -- Configuration to override the default [application.yaml](https://github.com/feast-dev/feast/blob/master/core/src/main/resources/application.yml). Will be created as a ConfigMap. `application-override.yaml` has a higher precedence than `application-secret.yaml` +application-override.yaml: + enabled: true + gcpServiceAccount: - # useExistingSecret specifies Feast to use an existing secret containing Google - # Cloud service account JSON key file. - useExistingSecret: false + # gcpServiceAccount.enabled -- Flag to use [service account](https://cloud.google.com/iam/docs/creating-managing-service-account-keys) JSON key + enabled: false existingSecret: - # name is the secret name of the existing secret for the service account. - name: feast-gcp-service-account - # key is the secret key of the existing secret for the service account. - # key is normally derived from the file name of the JSON key file. - key: key.json - # mountPath is the directory path where the JSON key file will be mounted. - # the value of "existingSecret.key" is file name of the service account file. - mountPath: /etc/gcloud/service-accounts - -# jvmOptions are options that will be passed to the Java Virtual Machine (JVM) -# running Feast Core. -# -# For example, it is good practice to set min and max heap size in JVM. -# https://stackoverflow.com/questions/6902135/side-effect-for-increasing-maxpermsize-and-max-heap-size -# -# Refer to https://docs.oracle.com/cd/E22289_01/html/821-1274/configuring-the-default-jvm-and-java-arguments.html -# to see other JVM options that can be set. -# -# jvmOptions: -# - -Xms1024m -# - -Xmx1024m + # gcpServiceAccount.existingSecret.name -- Name of the existing secret containing the service account + name: feast-gcp-service-account + # gcpServiceAccount.existingSecret.key -- Key in the secret data (file name of the service account) + key: credentials.json -livenessProbe: +postgresql: + # postgresql.existingSecret -- Existing secret to use for authenticating to Postgres + existingSecret: "" + +# gcpProjectId -- Project ID to use when using Google Cloud services such as BigQuery, Cloud Storage and Dataflow +gcpProjectId: "" + +# javaOpts -- [JVM options](https://docs.oracle.com/cd/E22289_01/html/821-1274/configuring-the-default-jvm-and-java-arguments.html). For better performance, it is advised to set the min and max heap:
`-Xms2048m -Xmx2048m` +javaOpts: + +# logType -- Log format, either `JSON` or `Console` +logType: Console +# logLevel -- Default log level, use either one of `DEBUG`, `INFO`, `WARN` or `ERROR` +logLevel: WARN + +prometheus: + # prometheus.enabled -- Flag to enable scraping of Feast Core metrics enabled: true + +# By default we disable the liveness probe, since if the DB fails restarting core will not result +# in application healing. +livenessProbe: + # livenessProbe.enabled -- Flag to enabled the probe + enabled: false + # livenessProbe.initialDelaySeconds -- Delay before the probe is initiated initialDelaySeconds: 60 + # livenessProbe.periodSeconds -- How often to perform the probe periodSeconds: 10 + # livenessProbe.timeoutSeconds -- When the probe times out timeoutSeconds: 5 + # livenessProbe.successThreshold -- Min consecutive success for the probe to be considered successful successThreshold: 1 + # livenessProbe.failureThreshold -- Min consecutive failures for the probe to be considered failed failureThreshold: 5 readinessProbe: + # readinessProbe.enabled -- Flag to enabled the probe enabled: true - initialDelaySeconds: 15 + # readinessProbe.initialDelaySeconds -- Delay before the probe is initiated + initialDelaySeconds: 20 + # readinessProbe.periodSeconds -- How often to perform the probe periodSeconds: 10 + # readinessProbe.timeoutSeconds -- When the probe times out timeoutSeconds: 10 + # readinessProbe.successThreshold -- Min consecutive success for the probe to be considered successful successThreshold: 1 + # readinessProbe.failureThreshold -- Min consecutive failures for the probe to be considered failed failureThreshold: 5 service: + # service.type -- Kubernetes service type type: ClusterIP http: + # service.http.port -- Service port for HTTP requests port: 80 + # service.http.targetPort -- Container port serving HTTP requests and Prometheus metrics targetPort: 8080 - # nodePort is the port number that each cluster node will listen to - # https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport - # - # nodePort: + # service.http.nodePort -- Port number that each cluster node will listen to + nodePort: grpc: + # service.grpc.port -- Service port for GRPC requests port: 6565 + # service.grpc.targetPort -- Container port serving GRPC requests targetPort: 6565 - # nodePort is the port number that each cluster node will listen to - # https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport - # - # nodePort: + # service.grpc.nodePort -- Port number that each cluster node will listen to + nodePort: ingress: - enabled: false - annotations: {} - # kubernetes.io/ingress.class: nginx - hosts: - # - host: chart-example.local - # port: http + grpc: + # ingress.grpc.enabled -- Flag to create an ingress resource for the service + enabled: false + # ingress.grpc.class -- Which ingress controller to use + class: nginx + # ingress.grpc.hosts -- List of hostnames to match when routing requests + hosts: [] + # ingress.grpc.annotations -- Extra annotations for the ingress + annotations: {} + https: + # ingress.grpc.https.enabled -- Flag to enable HTTPS + enabled: true + # ingress.grpc.https.secretNames -- Map of hostname to TLS secret name + secretNames: {} + # ingress.grpc.whitelist -- Allowed client IP source ranges + whitelist: "" + auth: + # ingress.grpc.auth.enabled -- Flag to enable auth + enabled: false + http: + # ingress.http.enabled -- Flag to create an ingress resource for the service + enabled: false + # ingress.http.class -- Which ingress controller to use + class: nginx + # ingress.http.hosts -- List of hostnames to match when routing requests + hosts: [] + # ingress.http.annotations -- Extra annotations for the ingress + annotations: {} + https: + # ingress.http.https.enabled -- Flag to enable HTTPS + enabled: true + # ingress.http.https.secretNames -- Map of hostname to TLS secret name + secretNames: {} + # ingress.http.whitelist -- Allowed client IP source ranges + whitelist: "" + auth: + # ingress.http.auth.enabled -- Flag to enable auth + enabled: false + # ingress.http.auth.authUrl -- URL to an existing authentication service + authUrl: http://auth-server.auth-ns.svc.cluster.local/auth +# resources -- CPU/memory [resource requests/limit](https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#resource-requests-and-limits-of-pod-and-container) resources: {} - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - # - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi +# nodeSelector -- Node labels for pod assignment nodeSelector: {} -tolerations: [] - -affinity: {} +# envOverrides -- Extra environment variables to set +envOverrides: {} \ No newline at end of file diff --git a/infra/charts/feast/charts/feast-serving/.helmignore b/infra/charts/feast/charts/feast-serving/.helmignore deleted file mode 100644 index 50af0317254..00000000000 --- a/infra/charts/feast/charts/feast-serving/.helmignore +++ /dev/null @@ -1,22 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ -# Common backup files -*.swp -*.bak -*.tmp -*~ -# Various IDEs -.project -.idea/ -*.tmproj -.vscode/ diff --git a/infra/charts/feast/charts/feast-serving/Chart.yaml b/infra/charts/feast/charts/feast-serving/Chart.yaml index 8486275d94f..7c8e6131cf7 100644 --- a/infra/charts/feast/charts/feast-serving/Chart.yaml +++ b/infra/charts/feast/charts/feast-serving/Chart.yaml @@ -1,4 +1,4 @@ apiVersion: v1 -description: A Helm chart for serving component of Feast +description: Feast Serving serves low-latency latest features and historical batch features. name: feast-serving -version: 0.3.2 +version: 0.5.0-alpha.1 diff --git a/infra/charts/feast/charts/feast-serving/README.md b/infra/charts/feast/charts/feast-serving/README.md new file mode 100644 index 00000000000..d30da2eb3a8 --- /dev/null +++ b/infra/charts/feast/charts/feast-serving/README.md @@ -0,0 +1,69 @@ +feast-serving +============= +Feast Serving serves low-latency latest features and historical batch features. + +Current chart version is `0.5.0-alpha.1` + + + + + +## Chart Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| "application-generated.yaml".enabled | bool | `true` | Flag to include Helm generated configuration for Feast Core host, Redis store and job store. This is useful for deployment that uses default configuration for Redis. Please set `application-override.yaml` to override this configuration. | +| "application-override.yaml" | object | `{"enabled":true}` | Configuration to override the default [application.yaml](https://github.com/feast-dev/feast/blob/master/serving/src/main/resources/application.yml). Will be created as a ConfigMap. `application-override.yaml` has a higher precedence than `application-secret.yaml` | +| "application-secret.yaml" | object | `{"enabled":true}` | Configuration to override the default [application.yaml](https://github.com/feast-dev/feast/blob/master/serving/src/main/resources/application.yml). Will be created as a Secret. `application-override.yaml` has a higher precedence than `application-secret.yaml`. It is recommended to either set `application-override.yaml` or `application-secret.yaml` only to simplify config management. | +| "application.yaml".enabled | bool | `true` | Flag to include the default [configuration](https://github.com/feast-dev/feast/blob/master/serving/src/main/resources/application.yml). Please set `application-override.yaml` to override this configuration. | +| envOverrides | object | `{}` | Extra environment variables to set | +| gcpProjectId | string | `""` | Project ID to use when using Google Cloud services such as BigQuery, Cloud Storage and Dataflow | +| gcpServiceAccount.enabled | bool | `false` | Flag to use [service account](https://cloud.google.com/iam/docs/creating-managing-service-account-keys) JSON key | +| gcpServiceAccount.existingSecret.key | string | `"credentials.json"` | Key in the secret data (file name of the service account) | +| gcpServiceAccount.existingSecret.name | string | `"feast-gcp-service-account"` | Name of the existing secret containing the service account | +| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy | +| image.repository | string | `"gcr.io/kf-feast/feast-serving"` | Docker image repository | +| image.tag | string | `"dev"` | Image tag | +| ingress.grpc.annotations | object | `{}` | Extra annotations for the ingress | +| ingress.grpc.auth.enabled | bool | `false` | Flag to enable auth | +| ingress.grpc.class | string | `"nginx"` | Which ingress controller to use | +| ingress.grpc.enabled | bool | `false` | Flag to create an ingress resource for the service | +| ingress.grpc.hosts | list | `[]` | List of hostnames to match when routing requests | +| ingress.grpc.https.enabled | bool | `true` | Flag to enable HTTPS | +| ingress.grpc.https.secretNames | object | `{}` | Map of hostname to TLS secret name | +| ingress.grpc.whitelist | string | `""` | Allowed client IP source ranges | +| ingress.http.annotations | object | `{}` | Extra annotations for the ingress | +| ingress.http.auth.authUrl | string | `"http://auth-server.auth-ns.svc.cluster.local/auth"` | URL to an existing authentication service | +| ingress.http.auth.enabled | bool | `false` | Flag to enable auth | +| ingress.http.class | string | `"nginx"` | Which ingress controller to use | +| ingress.http.enabled | bool | `false` | Flag to create an ingress resource for the service | +| ingress.http.hosts | list | `[]` | List of hostnames to match when routing requests | +| ingress.http.https.enabled | bool | `true` | Flag to enable HTTPS | +| ingress.http.https.secretNames | object | `{}` | Map of hostname to TLS secret name | +| ingress.http.whitelist | string | `""` | Allowed client IP source ranges | +| javaOpts | string | `nil` | [JVM options](https://docs.oracle.com/cd/E22289_01/html/821-1274/configuring-the-default-jvm-and-java-arguments.html). For better performance, it is advised to set the min and max heap:
`-Xms2048m -Xmx2048m` | +| livenessProbe.enabled | bool | `true` | Flag to enabled the probe | +| livenessProbe.failureThreshold | int | `5` | Min consecutive failures for the probe to be considered failed | +| livenessProbe.initialDelaySeconds | int | `60` | Delay before the probe is initiated | +| livenessProbe.periodSeconds | int | `10` | How often to perform the probe | +| livenessProbe.successThreshold | int | `1` | Min consecutive success for the probe to be considered successful | +| livenessProbe.timeoutSeconds | int | `5` | When the probe times out | +| logLevel | string | `"WARN"` | Default log level, use either one of `DEBUG`, `INFO`, `WARN` or `ERROR` | +| logType | string | `"Console"` | Log format, either `JSON` or `Console` | +| nodeSelector | object | `{}` | Node labels for pod assignment | +| prometheus.enabled | bool | `true` | Flag to enable scraping of Feast Core metrics | +| readinessProbe.enabled | bool | `true` | Flag to enabled the probe | +| readinessProbe.failureThreshold | int | `5` | Min consecutive failures for the probe to be considered failed | +| readinessProbe.initialDelaySeconds | int | `15` | Delay before the probe is initiated | +| readinessProbe.periodSeconds | int | `10` | How often to perform the probe | +| readinessProbe.successThreshold | int | `1` | Min consecutive success for the probe to be considered successful | +| readinessProbe.timeoutSeconds | int | `10` | When the probe times out | +| replicaCount | int | `1` | Number of pods that will be created | +| resources | object | `{}` | CPU/memory [resource requests/limit](https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#resource-requests-and-limits-of-pod-and-container) | +| service.grpc.nodePort | string | `nil` | Port number that each cluster node will listen to | +| service.grpc.port | int | `6566` | Service port for GRPC requests | +| service.grpc.targetPort | int | `6566` | Container port serving GRPC requests | +| service.http.nodePort | string | `nil` | Port number that each cluster node will listen to | +| service.http.port | int | `80` | Service port for HTTP requests | +| service.http.targetPort | int | `8080` | Container port serving HTTP requests | +| service.type | string | `"ClusterIP"` | Kubernetes service type | diff --git a/infra/charts/feast/charts/feast-serving/charts/redis-9.5.0.tgz b/infra/charts/feast/charts/feast-serving/charts/redis-9.5.0.tgz deleted file mode 100644 index 962893a825d..00000000000 Binary files a/infra/charts/feast/charts/feast-serving/charts/redis-9.5.0.tgz and /dev/null differ diff --git a/infra/charts/feast/charts/feast-serving/requirements.yaml b/infra/charts/feast/charts/feast-serving/requirements.yaml deleted file mode 100644 index fa4c1df4c10..00000000000 --- a/infra/charts/feast/charts/feast-serving/requirements.yaml +++ /dev/null @@ -1,5 +0,0 @@ -dependencies: -- name: redis - version: 9.5.0 - repository: "@stable" - condition: redis.enabled diff --git a/infra/charts/feast/charts/feast-serving/templates/_ingress.yaml b/infra/charts/feast/charts/feast-serving/templates/_ingress.yaml new file mode 100644 index 00000000000..5bed6df0470 --- /dev/null +++ b/infra/charts/feast/charts/feast-serving/templates/_ingress.yaml @@ -0,0 +1,68 @@ +{{- /* +This takes an array of three values: +- the top context +- the feast component +- the service protocol +- the ingress context +*/ -}} +{{- define "feast.ingress" -}} +{{- $top := (index . 0) -}} +{{- $component := (index . 1) -}} +{{- $protocol := (index . 2) -}} +{{- $ingressValues := (index . 3) -}} +apiVersion: extensions/v1beta1 +kind: Ingress +{{ include "feast.ingress.metadata" . }} +spec: + rules: + {{- range $host := $ingressValues.hosts }} + - host: {{ $host }} + http: + paths: + - path: / + backend: + serviceName: {{ include (printf "feast-%s.fullname" $component) $top }} + servicePort: {{ index $top.Values "service" $protocol "port" }} + {{- end }} +{{- if $ingressValues.https.enabled }} + tls: + {{- range $host := $ingressValues.hosts }} + - secretName: {{ index $ingressValues.https.secretNames $host | default (splitList "." $host | rest | join "-" | printf "%s-tls") }} + hosts: + - {{ $host }} + {{- end }} +{{- end -}} +{{- end -}} + +{{- define "feast.ingress.metadata" -}} +{{- $commonMetadata := fromYaml (include "common.metadata" (first .)) }} +{{- $overrides := fromYaml (include "feast.ingress.metadata-overrides" .) -}} +{{- toYaml (merge $overrides $commonMetadata) -}} +{{- end -}} + +{{- define "feast.ingress.metadata-overrides" -}} +{{- $top := (index . 0) -}} +{{- $component := (index . 1) -}} +{{- $protocol := (index . 2) -}} +{{- $ingressValues := (index . 3) -}} +{{- $commonFullname := include "common.fullname" $top }} +metadata: + name: {{ $commonFullname }}-{{ $component }}-{{ $protocol }} + annotations: + kubernetes.io/ingress.class: {{ $ingressValues.class | quote }} + {{- if (and (eq $ingressValues.class "nginx") $ingressValues.auth.enabled) }} + nginx.ingress.kubernetes.io/auth-url: {{ $ingressValues.auth.authUrl | quote }} + nginx.ingress.kubernetes.io/auth-response-headers: "x-auth-request-email, x-auth-request-user" + nginx.ingress.kubernetes.io/auth-signin: "https://{{ $ingressValues.auth.signinHost | default (splitList "." (index $ingressValues.hosts 0) | rest | join "." | printf "auth.%s")}}/oauth2/start?rd=/r/$host/$request_uri" + {{- end }} + {{- if (and (eq $ingressValues.class "nginx") $ingressValues.whitelist) }} + nginx.ingress.kubernetes.io/whitelist-source-range: {{ $ingressValues.whitelist | quote -}} + {{- end }} + {{- if (and (eq $ingressValues.class "nginx") (eq $protocol "grpc") ) }} + # TODO: Allow choice of GRPC/GRPCS + nginx.ingress.kubernetes.io/backend-protocol: "GRPC" + {{- end }} + {{- if $ingressValues.annotations -}} + {{ include "common.annote" $ingressValues.annotations | indent 4 }} + {{- end }} +{{- end -}} diff --git a/infra/charts/feast/charts/feast-serving/templates/configmap.yaml b/infra/charts/feast/charts/feast-serving/templates/configmap.yaml index 0ec80252c16..011b4cbccab 100644 --- a/infra/charts/feast/charts/feast-serving/templates/configmap.yaml +++ b/infra/charts/feast/charts/feast-serving/templates/configmap.yaml @@ -10,38 +10,31 @@ metadata: release: {{ .Release.Name }} heritage: {{ .Release.Service }} data: - application.yaml: | -{{- $config := index .Values "application.yaml" }} - -{{- if .Values.core.enabled }} -{{- $newConfig := dict "feast" (dict "core-host" (printf "%s-feast-core" .Release.Name)) }} -{{- $config := mergeOverwrite $config $newConfig }} -{{- end }} - -{{- $store := index .Values "store.yaml" }} -{{- if and (eq $store.type "BIGQUERY") (not (hasKey $config.feast.jobs "store-options")) }} -{{- $jobStore := dict "host" (printf "%s-redis-headless" .Release.Name) "port" 6379 }} -{{- $newConfig := dict "feast" (dict "jobs" (dict "store-options" $jobStore)) }} -{{- $config := mergeOverwrite $config $newConfig }} -{{- end }} - -{{- toYaml $config | nindent 4 }} - - store.yaml: | -{{- $config := index .Values "store.yaml"}} - -{{- if and .Values.redis.enabled (eq $config.type "REDIS") }} - -{{- if eq .Values.redis.master.service.type "ClusterIP" }} -{{- $newConfig := dict "redis_config" (dict "host" (printf "%s-redis-headless" .Release.Name) "port" .Values.redis.redisPort) }} -{{- $config := mergeOverwrite $config $newConfig }} + application-generated.yaml: | +{{- if index .Values "application-generated.yaml" "enabled" }} + feast: + core-host: {{ .Release.Name }}-feast-core + + stores: + - name: online + type: REDIS + config: + host: {{ .Release.Name }}-redis-master + port: 6379 + subscriptions: + - name: "*" + project: "*" + version: "*" + + job_store: + redis_host: {{ .Release.Name }}-redis-master + redis_port: 6379 + + server: + port: {{ .Values.service.http.targetPort }} {{- end }} -{{- if and (eq .Values.redis.master.service.type "LoadBalancer") (not (empty .Values.redis.master.service.loadBalancerIP)) }} -{{- $newConfig := dict "redis_config" (dict "host" .Values.redis.master.service.loadBalancerIP "port" .Values.redis.redisPort) }} -{{- $config := mergeOverwrite $config $newConfig }} + application-override.yaml: | +{{- if index .Values "application-override.yaml" "enabled" }} +{{- toYaml (index .Values "application-override.yaml") | nindent 4 }} {{- end }} - -{{- end }} - -{{- toYaml $config | nindent 4 }} diff --git a/infra/charts/feast/charts/feast-serving/templates/deployment.yaml b/infra/charts/feast/charts/feast-serving/templates/deployment.yaml index 5be636df96b..bb8fdc55ae5 100644 --- a/infra/charts/feast/charts/feast-serving/templates/deployment.yaml +++ b/infra/charts/feast/charts/feast-serving/templates/deployment.yaml @@ -19,10 +19,11 @@ spec: template: metadata: annotations: + checksum/configmap: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} {{- if .Values.prometheus.enabled }} - {{ $config := index .Values "application.yaml" }} prometheus.io/path: /metrics - prometheus.io/port: "{{ $config.server.port }}" + prometheus.io/port: "{{ .Values.service.http.targetPort }}" prometheus.io/scrape: "true" {{- end }} labels: @@ -39,45 +40,80 @@ spec: - name: {{ template "feast-serving.fullname" . }}-config configMap: name: {{ template "feast-serving.fullname" . }} - {{- if .Values.gcpServiceAccount.useExistingSecret }} - - name: {{ template "feast-serving.fullname" . }}-gcpserviceaccount + - name: {{ template "feast-serving.fullname" . }}-secret + secret: + secretName: {{ template "feast-serving.fullname" . }} + {{- if .Values.gcpServiceAccount.enabled }} + - name: {{ template "feast-serving.fullname" . }}-gcp-service-account secret: secretName: {{ .Values.gcpServiceAccount.existingSecret.name }} {{- end }} containers: - name: {{ .Chart.Name }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + image: {{ .Values.image.repository }}:{{ .Values.image.tag }} imagePullPolicy: {{ .Values.image.pullPolicy }} - + volumeMounts: - name: {{ template "feast-serving.fullname" . }}-config - mountPath: "{{ .Values.springConfigMountPath }}" - {{- if .Values.gcpServiceAccount.useExistingSecret }} - - name: {{ template "feast-serving.fullname" . }}-gcpserviceaccount - mountPath: {{ .Values.gcpServiceAccount.mountPath }} + mountPath: /etc/feast + - name: {{ template "feast-serving.fullname" . }}-secret + mountPath: /etc/secrets/feast + readOnly: true + {{- if .Values.gcpServiceAccount.enabled }} + - name: {{ template "feast-serving.fullname" . }}-gcp-service-account + mountPath: /etc/secrets/google readOnly: true {{- end }} env: - {{- if .Values.gcpServiceAccount.useExistingSecret }} + - name: LOG_TYPE + value: {{ .Values.logType | quote }} + - name: LOG_LEVEL + value: {{ .Values.logLevel | quote }} + + {{- if .Values.gcpServiceAccount.enabled }} - name: GOOGLE_APPLICATION_CREDENTIALS - value: {{ .Values.gcpServiceAccount.mountPath }}/{{ .Values.gcpServiceAccount.existingSecret.key }} + value: /etc/secrets/google/{{ .Values.gcpServiceAccount.existingSecret.key }} + {{- end }} + + {{- if .Values.gcpProjectId }} + - name: GOOGLE_CLOUD_PROJECT + value: {{ .Values.gcpProjectId | quote }} + {{- end }} + + {{- if .Values.javaOpts }} + - name: JAVA_TOOL_OPTIONS + value: {{ .Values.javaOpts }} + {{- end }} + + {{- range $key, $value := .Values.envOverrides }} + - name: {{ printf "%s" $key | replace "." "_" | upper | quote }} + value: {{ $value | quote }} {{- end }} command: - java - {{- range .Values.jvmOptions }} - - {{ . }} - {{- end }} - - -jar + - -jar - /opt/feast/feast-serving.jar - - "--spring.config.location=file:{{ .Values.springConfigMountPath }}/application.yaml" + - --spring.config.location= + {{- if index .Values "application.yaml" "enabled" -}} + classpath:/application.yml + {{- end }} + {{- if index .Values "application-generated.yaml" "enabled" -}} + ,file:/etc/feast/application-generated.yaml + {{- end }} + {{- if index .Values "application-secret.yaml" "enabled" -}} + ,file:/etc/secrets/feast/application-secret.yaml + {{- end }} + {{- if index .Values "application-override.yaml" "enabled" -}} + ,file:/etc/feast/application-override.yaml + {{- end }} ports: - name: http containerPort: {{ .Values.service.http.targetPort }} - - name: grpc + - name: grpc containerPort: {{ .Values.service.grpc.targetPort }} {{- if .Values.livenessProbe.enabled }} @@ -101,6 +137,6 @@ spec: timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} failureThreshold: {{ .Values.readinessProbe.failureThreshold }} {{- end }} - + resources: {{- toYaml .Values.resources | nindent 10 }} diff --git a/infra/charts/feast/charts/feast-serving/templates/ingress.yaml b/infra/charts/feast/charts/feast-serving/templates/ingress.yaml index c6b4cb07a81..1bcd176147a 100644 --- a/infra/charts/feast/charts/feast-serving/templates/ingress.yaml +++ b/infra/charts/feast/charts/feast-serving/templates/ingress.yaml @@ -1,28 +1,7 @@ -{{- if .Values.ingress.enabled -}} -{{- $fullName := include "feast-serving.fullname" . -}} -apiVersion: extensions/v1beta1 -kind: Ingress -metadata: - name: {{ $fullName }} - labels: - app: {{ template "feast-serving.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version }} - component: serving - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - annotations: -{{- with .Values.ingress.annotations }} -{{ toYaml . | indent 4 }} +{{- if .Values.ingress.http.enabled -}} +{{ template "feast.ingress" (list . "serving" "http" .Values.ingress.http) }} {{- end }} -spec: - rules: - {{- range .Values.ingress.hosts }} - - host: {{ .host | quote }} - http: - paths: - - path: / - backend: - serviceName: {{ $fullName }} - servicePort: {{ .port | quote }} - {{- end }} +--- +{{ if .Values.ingress.grpc.enabled -}} +{{ template "feast.ingress" (list . "serving" "grpc" .Values.ingress.grpc) }} {{- end }} diff --git a/infra/charts/feast/charts/feast-serving/templates/secret.yaml b/infra/charts/feast/charts/feast-serving/templates/secret.yaml new file mode 100644 index 00000000000..2ccbccfcf7b --- /dev/null +++ b/infra/charts/feast/charts/feast-serving/templates/secret.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "feast-serving.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "feast-serving.name" . }} + component: serving + chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +type: Opaque +stringData: + application-secret.yaml: | +{{- toYaml (index .Values "application-secret.yaml") | nindent 4 }} diff --git a/infra/charts/feast/charts/feast-serving/values.yaml b/infra/charts/feast/charts/feast-serving/values.yaml index d489a48748d..c94c7205264 100644 --- a/infra/charts/feast/charts/feast-serving/values.yaml +++ b/infra/charts/feast/charts/feast-serving/values.yaml @@ -1,200 +1,148 @@ -# redis configures Redis that is installed as part of Feast Serving. -# Refer to https://github.com/helm/charts/tree/99430c4afdc88213c1ca08f40eeb03868ffcc9d7/stable/redis -# for additional configuration -redis: - # enabled specifies whether Redis should be installed as part of Feast Serving. - # - # If enabled, "redis_config" in store.yaml will be overwritten by Helm - # to the configuration in this Redis installation. - enabled: false - # usePassword specifies if password is required to access Redis. Note that - # Feast 0.3 does not support Redis with password. - usePassword: false - # cluster configuration for Redis. - cluster: - # enabled specifies if Redis should be installed in cluster mode. - enabled: false - -# core configures Feast Core in the same parent feast chart that this Feast -# Serving connects to. -core: - # enabled specifies that Feast Serving will use Feast Core installed - # in the same parent feast chart. If enabled, Helm will overwrite - # "feast.core-host" in application.yaml with the correct value. - enabled: true - -# replicaCount is the number of pods that will be created. +# replicaCount -- Number of pods that will be created replicaCount: 1 -# image configures the Docker image for Feast Serving image: + # image.repository -- Docker image repository repository: gcr.io/kf-feast/feast-serving - tag: 0.3.2 + # image.tag -- Image tag + tag: dev + # image.pullPolicy -- Image pull policy pullPolicy: IfNotPresent -# application.yaml is the main configuration for Feast Serving application. -# -# Feast Core is a Spring Boot app which uses this yaml configuration file. -# Refer to https://github.com/gojek/feast/blob/79eb4ab5fa3d37102c1dca9968162a98690526ba/serving/src/main/resources/application.yml -# for a complete list and description of the configuration. -# -# Note that some properties defined in application.yaml may be overridden by -# Helm under certain conditions. For example, if core is enabled, then -# "feast.core-host" will be overridden. Also, if "type: BIGQUERY" is specified -# in store.yaml, "feast.jobs.store-options" will be overridden as well with -# the default option supported in Feast 0.3. application.yaml: - feast: - version: 0.3 - core-host: localhost - core-grpc-port: 6565 - tracing: - enabled: false - tracer-name: jaeger - service-name: feast-serving - store: - config-path: /etc/feast/feast-serving/store.yaml - redis-pool-max-size: 128 - redis-pool-max-idle: 64 - jobs: - staging-location: "" - store-type: "" - store-options: {} - grpc: - port: 6566 - enable-reflection: true - server: - port: 8080 - -# store.yaml is the configuration for Feast Store. -# -# Refer to this link for description: -# https://github.com/gojek/feast/blob/79eb4ab5fa3d37102c1dca9968162a98690526ba/protos/feast/core/Store.proto -# -# Use the correct store configuration depending on whether the installed -# Feast Serving is "online" or "batch", by uncommenting the correct store.yaml. -# -# Note that if "redis.enabled: true" and "type: REDIS" in store.yaml, -# Helm will override "redis_config" with configuration of Redis installed -# in this chart. -# -# Note that if "type: BIGQUERY" in store.yaml, Helm assumes Feast Online serving -# is also installed with Redis store. Helm will then override "feast.jobs.store-options" -# in application.yaml with the installed Redis store configuration. This is -# because in Feast 0.3, Redis job store is required. -# -# store.yaml: -# name: online -# type: REDIS -# redis_config: -# host: localhost -# port: 6379 -# subscriptions: -# - project: "*" -# name: "*" -# version: "*" -# -# store.yaml: -# name: bigquery -# type: BIGQUERY -# bigquery_config: -# project_id: PROJECT_ID -# dataset_id: DATASET_ID -# subscriptions: -# - project: "*" -# name: "*" -# version: "*" - -# springConfigMountPath is the directory path where application.yaml and -# store.yaml will be mounted in the container. -springConfigMountPath: /etc/feast/feast-serving - -# gcpServiceAccount is the service account that Feast Serving will use. + # "application.yaml".enabled -- Flag to include the default [configuration](https://github.com/feast-dev/feast/blob/master/serving/src/main/resources/application.yml). Please set `application-override.yaml` to override this configuration. + enabled: true + +application-generated.yaml: + # "application-generated.yaml".enabled -- Flag to include Helm generated configuration for http port, Feast Core host, Redis store and job store. This is useful for deployment that uses default configuration for Redis. Please set `application-override.yaml` to override this configuration. + enabled: true + +# "application-secret.yaml" -- Configuration to override the default [application.yaml](https://github.com/feast-dev/feast/blob/master/serving/src/main/resources/application.yml). Will be created as a Secret. `application-override.yaml` has a higher precedence than `application-secret.yaml`. It is recommended to either set `application-override.yaml` or `application-secret.yaml` only to simplify config management. +application-secret.yaml: + enabled: true + +# "application-override.yaml" -- Configuration to override the default [application.yaml](https://github.com/feast-dev/feast/blob/master/serving/src/main/resources/application.yml). Will be created as a ConfigMap. `application-override.yaml` has a higher precedence than `application-secret.yaml` +application-override.yaml: + enabled: true + gcpServiceAccount: - # useExistingSecret specifies Feast to use an existing secret containing Google + # gcpServiceAccount.enabled -- Flag to use [service account](https://cloud.google.com/iam/docs/creating-managing-service-account-keys) JSON key # Cloud service account JSON key file. - useExistingSecret: false + enabled: false existingSecret: - # name is the secret name of the existing secret for the service account. - name: feast-gcp-service-account - # key is the secret key of the existing secret for the service account. - # key is normally derived from the file name of the JSON key file. - key: key.json - # mountPath is the directory path where the JSON key file will be mounted. - # the value of "existingSecret.key" is file name of the service account file. - mountPath: /etc/gcloud/service-accounts - -# jvmOptions are options that will be passed to the Java Virtual Machine (JVM) -# running Feast Core. -# -# For example, it is good practice to set min and max heap size in JVM. -# https://stackoverflow.com/questions/6902135/side-effect-for-increasing-maxpermsize-and-max-heap-size -# -# Refer to https://docs.oracle.com/cd/E22289_01/html/821-1274/configuring-the-default-jvm-and-java-arguments.html -# to see other JVM options that can be set. -# -# jvmOptions: -# - -Xms768m -# - -Xmx768m + # gcpServiceAccount.existingSecret.name -- Name of the existing secret containing the service account + name: feast-gcp-service-account + # gcpServiceAccount.existingSecret.key -- Key in the secret data (file name of the service account) + key: credentials.json + +# gcpProjectId -- Project ID to use when using Google Cloud services such as BigQuery, Cloud Storage and Dataflow +gcpProjectId: "" + +# javaOpts -- [JVM options](https://docs.oracle.com/cd/E22289_01/html/821-1274/configuring-the-default-jvm-and-java-arguments.html). For better performance, it is advised to set the min and max heap:
`-Xms2048m -Xmx2048m` +javaOpts: + +# logType -- Log format, either `JSON` or `Console` +logType: Console +# logLevel -- Default log level, use either one of `DEBUG`, `INFO`, `WARN` or `ERROR` +logLevel: WARN + +prometheus: + # prometheus.enabled -- Flag to enable scraping of Feast Core metrics + enabled: true livenessProbe: - enabled: false + # livenessProbe.enabled -- Flag to enabled the probe + enabled: true + # livenessProbe.initialDelaySeconds -- Delay before the probe is initiated initialDelaySeconds: 60 + # livenessProbe.periodSeconds -- How often to perform the probe periodSeconds: 10 + # livenessProbe.timeoutSeconds -- When the probe times out timeoutSeconds: 5 + # livenessProbe.successThreshold -- Min consecutive success for the probe to be considered successful successThreshold: 1 + # livenessProbe.failureThreshold -- Min consecutive failures for the probe to be considered failed failureThreshold: 5 readinessProbe: - enabled: false + # readinessProbe.enabled -- Flag to enabled the probe + enabled: true + # readinessProbe.initialDelaySeconds -- Delay before the probe is initiated initialDelaySeconds: 15 + # readinessProbe.periodSeconds -- How often to perform the probe periodSeconds: 10 + # readinessProbe.timeoutSeconds -- When the probe times out timeoutSeconds: 10 + # readinessProbe.successThreshold -- Min consecutive success for the probe to be considered successful successThreshold: 1 + # readinessProbe.failureThreshold -- Min consecutive failures for the probe to be considered failed failureThreshold: 5 service: + # service.type -- Kubernetes service type type: ClusterIP http: + # service.http.port -- Service port for HTTP requests port: 80 + # service.http.targetPort -- Container port serving HTTP requests and Prometheus metrics targetPort: 8080 - # nodePort is the port number that each cluster node will listen to - # https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport - # - # nodePort: + # service.http.nodePort -- Port number that each cluster node will listen to + nodePort: grpc: + # service.grpc.port -- Service port for GRPC requests port: 6566 + # service.grpc.targetPort -- Container port serving GRPC requests targetPort: 6566 - # nodePort is the port number that each cluster node will listen to - # https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport - # - # nodePort: + # service.grpc.nodePort -- Port number that each cluster node will listen to + nodePort: ingress: - enabled: false - annotations: {} - # kubernetes.io/ingress.class: nginx - hosts: - # - host: chart-example.local - # port: http - -prometheus: - enabled: true + grpc: + # ingress.grpc.enabled -- Flag to create an ingress resource for the service + enabled: false + # ingress.grpc.class -- Which ingress controller to use + class: nginx + # ingress.grpc.hosts -- List of hostnames to match when routing requests + hosts: [] + # ingress.grpc.annotations -- Extra annotations for the ingress + annotations: {} + https: + # ingress.grpc.https.enabled -- Flag to enable HTTPS + enabled: true + # ingress.grpc.https.secretNames -- Map of hostname to TLS secret name + secretNames: {} + # ingress.grpc.whitelist -- Allowed client IP source ranges + whitelist: "" + auth: + # ingress.grpc.auth.enabled -- Flag to enable auth + enabled: false + http: + # ingress.http.enabled -- Flag to create an ingress resource for the service + enabled: false + # ingress.http.class -- Which ingress controller to use + class: nginx + # ingress.http.hosts -- List of hostnames to match when routing requests + hosts: [] + # ingress.http.annotations -- Extra annotations for the ingress + annotations: {} + https: + # ingress.http.https.enabled -- Flag to enable HTTPS + enabled: true + # ingress.http.https.secretNames -- Map of hostname to TLS secret name + secretNames: {} + # ingress.http.whitelist -- Allowed client IP source ranges + whitelist: "" + auth: + # ingress.http.auth.enabled -- Flag to enable auth + enabled: false + # ingress.http.auth.authUrl -- URL to an existing authentication service + authUrl: http://auth-server.auth-ns.svc.cluster.local/auth +# resources -- CPU/memory [resource requests/limit](https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#resource-requests-and-limits-of-pod-and-container) resources: {} - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi +# nodeSelector -- Node labels for pod assignment nodeSelector: {} -tolerations: [] - -affinity: {} +# envOverrides -- Extra environment variables to set +envOverrides: {} \ No newline at end of file diff --git a/infra/charts/feast/charts/grafana-5.0.5.tgz b/infra/charts/feast/charts/grafana-5.0.5.tgz new file mode 100644 index 00000000000..06eb83a5e58 Binary files /dev/null and b/infra/charts/feast/charts/grafana-5.0.5.tgz differ diff --git a/infra/charts/feast/charts/kafka-0.20.8.tgz b/infra/charts/feast/charts/kafka-0.20.8.tgz new file mode 100644 index 00000000000..f61be294aea Binary files /dev/null and b/infra/charts/feast/charts/kafka-0.20.8.tgz differ diff --git a/infra/charts/feast/charts/postgresql-8.6.1.tgz b/infra/charts/feast/charts/postgresql-8.6.1.tgz new file mode 100644 index 00000000000..c1ee74e8e82 Binary files /dev/null and b/infra/charts/feast/charts/postgresql-8.6.1.tgz differ diff --git a/infra/charts/feast/charts/prometheus-11.0.2.tgz b/infra/charts/feast/charts/prometheus-11.0.2.tgz new file mode 100644 index 00000000000..32b3abbe4e5 Binary files /dev/null and b/infra/charts/feast/charts/prometheus-11.0.2.tgz differ diff --git a/infra/charts/feast/charts/prometheus-statsd-exporter/README.md b/infra/charts/feast/charts/prometheus-statsd-exporter/README.md index 69eb33039b8..8a6739f393b 100644 --- a/infra/charts/feast/charts/prometheus-statsd-exporter/README.md +++ b/infra/charts/feast/charts/prometheus-statsd-exporter/README.md @@ -33,7 +33,8 @@ $ helm delete my-release ## Configuration - |Parameter | Description | Default | +|Parameter | Description | Default | +|----------------------------|------------------------------------------------------|----------------------------------------| |`extraArgs` | key:value list of extra arguments to give the binary | `{}` | |`image.pullPolicy` | Image pull policy | `IfNotPresent` | |`image.repository` | Image repository | `prom/statsd-exporter` | diff --git a/infra/charts/feast/charts/prometheus-statsd-exporter/templates/deployment.yaml b/infra/charts/feast/charts/prometheus-statsd-exporter/templates/deployment.yaml index 47308ef89bd..44fac913b6a 100644 --- a/infra/charts/feast/charts/prometheus-statsd-exporter/templates/deployment.yaml +++ b/infra/charts/feast/charts/prometheus-statsd-exporter/templates/deployment.yaml @@ -1,4 +1,4 @@ -apiVersion: extensions/v1beta1 +apiVersion: apps/v1 kind: Deployment metadata: name: {{ template "prometheus-statsd-exporter.fullname" . }} @@ -16,7 +16,7 @@ spec: template: metadata: annotations: -{{ toYaml .Values.statsdexporter.annotations | indent 8 }} +{{ toYaml .Values.statsdexporter.podAnnotations | indent 8 }} labels: app: {{ template "prometheus-statsd-exporter.name" . }} release: {{ .Release.Name }} diff --git a/infra/charts/feast/charts/prometheus-statsd-exporter/templates/service.yaml b/infra/charts/feast/charts/prometheus-statsd-exporter/templates/service.yaml index 88d01b24a61..1690ba7fe09 100644 --- a/infra/charts/feast/charts/prometheus-statsd-exporter/templates/service.yaml +++ b/infra/charts/feast/charts/prometheus-statsd-exporter/templates/service.yaml @@ -15,6 +15,38 @@ metadata: {{ toYaml .Values.service.labels | indent 4 }} {{- end }} name: {{ template "prometheus-statsd-exporter.fullname" . }} +spec: + ports: + - name: metrics + port: {{ .Values.service.metricsPort }} + protocol: TCP + targetPort: 9102 + - name: statsd-tcp + port: {{ .Values.service.statsdPort }} + protocol: TCP + targetPort: 9125 + selector: + app: {{ template "prometheus-statsd-exporter.name" . }} + release: {{ .Release.Name }} + type: ClusterIP +--- +apiVersion: v1 +kind: Service +metadata: +{{- if .Values.service.annotations }} + annotations: +{{ toYaml .Values.service.annotations | indent 4 }} +{{- end }} + labels: + app: {{ template "prometheus-statsd-exporter.fullname" . }} + chart: {{ .Chart.Name }}-{{ .Chart.Version }} + component: "{{ .Chart.Name }}" + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +{{- if .Values.service.labels }} +{{ toYaml .Values.service.labels | indent 4 }} +{{- end }} + name: {{ template "prometheus-statsd-exporter.fullname" . }}-udp spec: {{- if .Values.service.clusterIP }} clusterIP: {{ .Values.service.clusterIP }} @@ -33,14 +65,6 @@ spec: {{- end }} {{- end }} ports: - - name: metrics - port: {{ .Values.service.metricsPort }} - protocol: TCP - targetPort: 9102 - - name: statsd-tcp - port: {{ .Values.service.statsdPort }} - protocol: TCP - targetPort: 9125 - name: statsd-udp port: {{ .Values.service.statsdPort }} protocol: UDP diff --git a/infra/charts/feast/charts/prometheus-statsd-exporter/values.yaml b/infra/charts/feast/charts/prometheus-statsd-exporter/values.yaml index f2d523771ea..b3f70ee264f 100644 --- a/infra/charts/feast/charts/prometheus-statsd-exporter/values.yaml +++ b/infra/charts/feast/charts/prometheus-statsd-exporter/values.yaml @@ -20,6 +20,9 @@ service: statsdexporter: podAnnotations: + prometheus.io/path: /metrics + prometheus.io/port: "9102" + prometheus.io/scrape: "true" extraArgs: {} # - --persistence.file=data-perst diff --git a/infra/charts/feast/charts/redis-10.5.6.tgz b/infra/charts/feast/charts/redis-10.5.6.tgz new file mode 100644 index 00000000000..f1e4ec41055 Binary files /dev/null and b/infra/charts/feast/charts/redis-10.5.6.tgz differ diff --git a/infra/charts/feast/files/img/dataflow-jobs.png b/infra/charts/feast/files/img/dataflow-jobs.png new file mode 100644 index 00000000000..2acf48f19e9 Binary files /dev/null and b/infra/charts/feast/files/img/dataflow-jobs.png differ diff --git a/infra/charts/feast/files/img/prometheus-server.png b/infra/charts/feast/files/img/prometheus-server.png new file mode 100644 index 00000000000..efe31dc9e13 Binary files /dev/null and b/infra/charts/feast/files/img/prometheus-server.png differ diff --git a/infra/charts/feast/requirements.lock b/infra/charts/feast/requirements.lock index 8afd9521573..c6ff995bacc 100644 --- a/infra/charts/feast/requirements.lock +++ b/infra/charts/feast/requirements.lock @@ -1,12 +1,30 @@ dependencies: - name: feast-core repository: "" - version: 0.3.2 + version: 0.5.0-alpha.1 - name: feast-serving repository: "" - version: 0.3.2 + version: 0.5.0-alpha.1 - name: feast-serving repository: "" - version: 0.3.2 -digest: sha256:7ee4cd271cbd4ace44817dd12ba65f490a8e3529adf199604a2c2bdad9c2fac3 -generated: "2019-11-27T13:35:41.334054+08:00" + version: 0.5.0-alpha.1 +- name: postgresql + repository: https://kubernetes-charts.storage.googleapis.com/ + version: 8.6.1 +- name: kafka + repository: https://kubernetes-charts-incubator.storage.googleapis.com/ + version: 0.20.8 +- name: redis + repository: https://kubernetes-charts.storage.googleapis.com/ + version: 10.5.6 +- name: prometheus-statsd-exporter + repository: "" + version: 0.1.2 +- name: prometheus + repository: https://kubernetes-charts.storage.googleapis.com/ + version: 11.0.2 +- name: grafana + repository: https://kubernetes-charts.storage.googleapis.com/ + version: 5.0.5 +digest: sha256:e325439384ef9b45428fbeafe8f1e230b331d4b5482c3f26f07b71cecae06c22 +generated: "2020-05-02T15:00:45.4365217+08:00" diff --git a/infra/charts/feast/requirements.yaml b/infra/charts/feast/requirements.yaml index ed280d64b6e..7de78705752 100644 --- a/infra/charts/feast/requirements.yaml +++ b/infra/charts/feast/requirements.yaml @@ -1,12 +1,35 @@ dependencies: - name: feast-core - version: 0.3.2 + version: 0.5.0-alpha.1 condition: feast-core.enabled - name: feast-serving - alias: feast-serving-batch - version: 0.3.2 - condition: feast-serving-batch.enabled + alias: feast-online-serving + version: 0.5.0-alpha.1 + condition: feast-online-serving.enabled - name: feast-serving - alias: feast-serving-online - version: 0.3.2 - condition: feast-serving-online.enabled + alias: feast-batch-serving + version: 0.5.0-alpha.1 + condition: feast-batch-serving.enabled +- name: postgresql + version: 8.6.1 + repository: https://kubernetes-charts.storage.googleapis.com/ + condition: postgresql.enabled +- name: kafka + version: 0.20.8 + repository: https://kubernetes-charts-incubator.storage.googleapis.com/ + condition: kafka.enabled +- name: redis + version: 10.5.6 + repository: https://kubernetes-charts.storage.googleapis.com/ + condition: redis.enabled +- name: prometheus-statsd-exporter + version: 0.1.2 + condition: prometheus-statsd-exporter.enabled +- name: prometheus + version: 11.0.2 + repository: https://kubernetes-charts.storage.googleapis.com/ + condition: prometheus.enabled +- name: grafana + version: 5.0.5 + repository: https://kubernetes-charts.storage.googleapis.com/ + condition: grafana.enabled diff --git a/infra/charts/feast/templates/tests/test-feast-batch-serving.yaml b/infra/charts/feast/templates/tests/test-feast-batch-serving.yaml new file mode 100644 index 00000000000..54173021d3b --- /dev/null +++ b/infra/charts/feast/templates/tests/test-feast-batch-serving.yaml @@ -0,0 +1,116 @@ +{{- if and (index .Values "feast-core" "enabled") (index .Values "feast-batch-serving" "enabled") }} + +apiVersion: v1 +kind: Pod +metadata: + name: "{{ .Release.Name }}-feast-batch-serving-test" + annotations: + "helm.sh/hook": test-success + namespace: {{ .Release.Namespace }} +spec: + containers: + - name: main + image: python:3.7 + command: + - bash + - -c + - | + pip install -U feast==0.4.* + + cat < featureset.yaml + kind: feature_set + spec: + name: customer_transactions + entities: + - name: customer_id + valueType: INT64 + features: + - name: daily_transactions + valueType: FLOAT + - name: total_transactions + valueType: FLOAT + maxAge: 3600s + EOF + + python < featureset.yaml + kind: feature_set + spec: + name: customer_transactions + entities: + - name: customer_id + valueType: INT64 + features: + - name: daily_transactions + valueType: FLOAT + - name: total_transactions + valueType: FLOAT + maxAge: 3600s + EOF + + python < + dataset_id: + staging_location: gs:///feast-staging-location + initial_retry_delay_seconds: 3 + total_timeout_seconds: 21600 + subscriptions: + - name: "*" + project: "*" + version: "*" + +postgresql: + existingSecret: feast-postgresql diff --git a/infra/charts/feast/values-dataflow-runner.yaml b/infra/charts/feast/values-dataflow-runner.yaml new file mode 100644 index 00000000000..0469a6349e2 --- /dev/null +++ b/infra/charts/feast/values-dataflow-runner.yaml @@ -0,0 +1,113 @@ +# values-dataflow-runner.yaml +feast-core: + gcpServiceAccount: + enabled: true + postgresql: + existingSecret: feast-postgresql + application-override.yaml: + feast: + stream: + options: + bootstrapServers: + jobs: + active_runner: dataflow + metrics: + host: + runners: + - name: dataflow + type: DataflowRunner + options: + project: + region: + zone: + tempLocation: + network: + subnetwork: + maxNumWorkers: 1 + autoscalingAlgorithm: THROUGHPUT_BASED + usePublicIps: false + workerMachineType: n1-standard-1 + deadLetterTableSpec: + +feast-online-serving: + application-override.yaml: + feast: + stores: + - name: online + type: REDIS + config: + host: + port: 6379 + subscriptions: + - name: "*" + project: "*" + version: "*" + +feast-batch-serving: + enabled: true + gcpServiceAccount: + enabled: true + application-override.yaml: + feast: + active_store: historical + stores: + - name: historical + type: BIGQUERY + config: + project_id: + dataset_id: + staging_location: gs:///feast-staging-location + initial_retry_delay_seconds: 3 + total_timeout_seconds: 21600 + subscriptions: + - name: "*" + project: "*" + version: "*" + +postgresql: + existingSecret: feast-postgresql + +kafka: + external: + enabled: true + type: LoadBalancer + annotations: + cloud.google.com/load-balancer-type: Internal + loadBalancerSourceRanges: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + firstListenerPort: 31090 + loadBalancerIP: + - + - + - + configurationOverrides: + "advertised.listeners": |- + EXTERNAL://${LOAD_BALANCER_IP}:31090 + "listener.security.protocol.map": |- + PLAINTEXT:PLAINTEXT,EXTERNAL:PLAINTEXT + "log.retention.hours": 1 + +redis: + master: + service: + type: LoadBalancer + loadBalancerIP: + annotations: + cloud.google.com/load-balancer-type: Internal + loadBalancerSourceRanges: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + +prometheus-statsd-exporter: + service: + type: LoadBalancer + annotations: + cloud.google.com/load-balancer-type: Internal + loadBalancerSourceRanges: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + loadBalancerIP: diff --git a/infra/charts/feast/values-demo.yaml b/infra/charts/feast/values-demo.yaml deleted file mode 100644 index fad4bc0afb0..00000000000 --- a/infra/charts/feast/values-demo.yaml +++ /dev/null @@ -1,71 +0,0 @@ -# The following are values for installing Feast for demonstration purpose: -# - Persistence is disabled since for demo purpose data is not expected -# to be durable -# - Only online serving (no batch serving) is installed to remove dependency -# on Google Cloud services. Batch serving requires BigQuery dependency. -# - Replace all occurrences of "feast.example.com" with the domain name or -# external IP pointing to your cluster -# - -feast-core: - enabled: true - - gcpServiceAccount: - useExistingSecret: false - - service: - type: NodePort - grpc: - nodePort: 32090 - - - resources: - requests: - cpu: 250m - memory: 256Mi - - postgresql: - persistence: - enabled: false - - - kafka: - enabled: true - persistence: - enabled: false - external: - enabled: true - type: NodePort - domain: feast.example.com - configurationOverrides: - "advertised.listeners": |- - EXTERNAL://feast.example.com:$((31090 + ${KAFKA_BROKER_ID})) - "listener.security.protocol.map": |- - PLAINTEXT:PLAINTEXT,EXTERNAL:PLAINTEXT - - application.yaml: - feast: - stream: - options: - bootstrapServers: feast.example.com:31090 - -feast-serving-online: - enabled: true - redis: - enabled: true - - service: - type: NodePort - grpc: - nodePort: 32091 - - store.yaml: - name: redis - type: REDIS - subscriptions: - - name: "*" - project: "*" - version: "*" - -feast-serving-batch: - enabled: false diff --git a/infra/charts/feast/values-external-store.yaml b/infra/charts/feast/values-external-store.yaml deleted file mode 100644 index d012bcec56c..00000000000 --- a/infra/charts/feast/values-external-store.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# TODO @dheryanto -# -# The following are sample values for installing Feast without setting up -# Kafka and Redis stores. In other words, using Feast with external stream -# source and stores. diff --git a/infra/charts/feast/values-production.yaml b/infra/charts/feast/values-production.yaml deleted file mode 100644 index 6b53dc19ea8..00000000000 --- a/infra/charts/feast/values-production.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# TODO @dheryanto -# -# The following are sample values for installing Feast for typical production -# environment. diff --git a/infra/charts/feast/values.yaml b/infra/charts/feast/values.yaml index ebc8c802a16..20ee2ab029f 100644 --- a/infra/charts/feast/values.yaml +++ b/infra/charts/feast/values.yaml @@ -1,205 +1,35 @@ -# Feast deployment installs the following components: -# - Feast Core -# - Feast Serving Online -# - Feast Serving Batch -# -# The configuration for different components can be referenced from: -# - charts/feast-core/values.yaml -# - charts/feast-serving/values.yaml -# -# Note that "feast-serving-online" and "feast-serving-batch" are -# aliases to "feast-serving" chart since in typical scenario two instances -# of Feast Serving: online and batch will be deployed. Both described -# using the same chart "feast-serving". -# -# The following are default values for typical Feast deployment, but not -# for production setting. Refer to "values-production.yaml" for recommended -# values in production environment. -# -# Note that the import job by default uses DirectRunner -# https://beam.apache.org/documentation/runners/direct/ -# in this configuration since it allows Feast to run in more environments -# (unlike DataflowRunner which requires Google Cloud services). -# -# A secret containing Google Cloud service account JSON key is required -# in this configuration. -# https://cloud.google.com/iam/docs/creating-managing-service-accounts -# -# The Google Cloud service account must have the following roles: -# - bigquery.dataEditor -# - bigquery.jobUser -# -# Assuming a service account JSON key file has been downloaded to -# (please name the file key.json): -# /home/user/key.json -# -# Run the following command to create the secret in your Kubernetes cluster: -# -# kubectl create secret generic feast-gcp-service-account \ -# --from-file=/home/user/key.json -# +feast-core: + # feast-core.enabled -- Flag to install Feast Core + enabled: true -# ============================================================ -# Feast Core -# ============================================================ +feast-online-serving: + # feast-online-serving.enabled -- Flag to install Feast Online Serving + enabled: true -feast-core: - # enabled specifies whether to install Feast Core component. - # - # Normally, this is set to "false" when Feast users need access to low latency - # Feast Serving, by deploying multiple instances of Feast Serving closest - # to the client. These instances of Feast Serving however can still use - # the same shared Feast Core. +feast-batch-serving: + # feast-batch-serving.enabled -- Flag to install Feast Batch Serving + enabled: false + +postgresql: + # postgresql.enabled -- Flag to install Postgresql enabled: true - # jvmOptions are options that will be passed to the Java Virtual Machine (JVM) - # running Feast Core. - # - # For example, it is good practice to set min and max heap size in JVM. - # https://stackoverflow.com/questions/6902135/side-effect-for-increasing-maxpermsize-and-max-heap-size - jvmOptions: - - -Xms1024m - - -Xmx1024m - # resources that should be allocated to Feast Core. - resources: - requests: - cpu: 1000m - memory: 1024Mi - limits: - memory: 2048Mi - # gcpServiceAccount is the Google service account that Feast Core will use. - gcpServiceAccount: - # useExistingSecret specifies Feast to use an existing secret containing - # Google Cloud service account JSON key file. - # - # This is the only supported option for now to use a service account JSON. - # Feast admin is expected to create this secret before deploying Feast. - useExistingSecret: true - existingSecret: - # name is the secret name of the existing secret for the service account. - name: feast-gcp-service-account - # key is the secret key of the existing secret for the service account. - # key is normally derived from the file name of the JSON key file. - key: key.json -# ============================================================ -# Feast Serving Online -# ============================================================ +kafka: + # kafka.enabled -- Flag to install Kafka + enabled: true + +redis: + # redis.enabled -- Flag to install Redis + enabled: true -feast-serving-online: - # enabled specifies whether to install Feast Serving Online component. +prometheus-statsd-exporter: + # prometheus-statsd-exporter.enabled -- Flag to install StatsD to Prometheus Exporter enabled: true - # redis.enabled specifies whether Redis should be installed as part of Feast Serving. - # - # If enabled is set to "false", Feast admin has to ensure there is an - # existing Redis running outside Feast, that Feast Serving can connect to. - redis: - enabled: true - # jvmOptions are options that will be passed to the Feast Serving JVM. - jvmOptions: - - -Xms1024m - - -Xmx1024m - # resources that should be allocated to Feast Serving. - resources: - requests: - cpu: 500m - memory: 1024Mi - limits: - memory: 2048Mi - # store.yaml is the configuration for Feast Store. - # - # Refer to this link for more description: - # https://github.com/gojek/feast/blob/79eb4ab5fa3d37102c1dca9968162a98690526ba/protos/feast/core/Store.proto - store.yaml: - name: redis - type: REDIS - redis_config: - # If redis.enabled is set to false, Feast admin should uncomment and - # set the host value to an "existing" Redis instance Feast will use as - # online Store. - # - # Else, if redis.enabled is set to true, no additional configuration is - # required. - # - # host: redis-host - port: 6379 - subscriptions: - - name: "*" - project: "*" - version: "*" -# ============================================================ -# Feast Serving Batch -# ============================================================ +prometheus: + # prometheus.enabled -- Flag to install Prometheus + enabled: true -feast-serving-batch: - # enabled specifies whether to install Feast Serving Batch component. +grafana: + # grafana.enabled -- Flag to install Grafana enabled: true - # redis.enabled specifies whether Redis should be installed as part of Feast Serving. - # - # This is usually set to "false" for Feast Serving Batch because the default - # store is BigQuery. - redis: - enabled: false - # jvmOptions are options that will be passed to the Feast Serving JVM. - jvmOptions: - - -Xms1024m - - -Xmx1024m - # resources that should be allocated to Feast Serving. - resources: - requests: - cpu: 500m - memory: 1024Mi - limits: - memory: 2048Mi - # gcpServiceAccount is the service account that Feast Serving will use. - gcpServiceAccount: - # useExistingSecret specifies Feast to use an existing secret containing - # Google Cloud service account JSON key file. - # - # This is the only supported option for now to use a service account JSON. - # Feast admin is expected to create this secret before deploying Feast. - useExistingSecret: true - existingSecret: - # name is the secret name of the existing secret for the service account. - name: feast-gcp-service-account - # key is the secret key of the existing secret for the service account. - # key is normally derived from the file name of the JSON key file. - key: key.json - # application.yaml is the main configuration for Feast Serving application. - # - # Feast Core is a Spring Boot app which uses this yaml configuration file. - # Refer to https://github.com/gojek/feast/blob/79eb4ab5fa3d37102c1dca9968162a98690526ba/serving/src/main/resources/application.yml - # for a complete list and description of the configuration. - application.yaml: - feast: - jobs: - # staging-location specifies the URI to store intermediate files for - # batch serving (required if using BigQuery as Store). - # - # Please set the value to an "existing" Google Cloud Storage URI that - # Feast serving has write access to. - staging-location: gs://bucket/path - # Type of store to store job metadata. - # - # This default configuration assumes that Feast Serving Online is - # enabled as well. So Feast Serving Batch will share the same - # Redis instance to store job statuses. - store-type: REDIS - # store.yaml is the configuration for Feast Store. - # - # Refer to this link for more description: - # https://github.com/gojek/feast/blob/79eb4ab5fa3d37102c1dca9968162a98690526ba/protos/feast/core/Store.proto - store.yaml: - name: bigquery - type: BIGQUERY - bigquery_config: - # project_id specifies the Google Cloud Project. Please set this to the - # project id you are using BigQuery in. - project_id: PROJECT_ID - # dataset_id specifies an "existing" BigQuery dataset Feast Serving Batch - # will use. Please ensure this dataset is created beforehand. - dataset_id: DATASET_ID - subscriptions: - - name: "*" - project: "*" - version: "*" diff --git a/infra/docker-compose/.env.sample b/infra/docker-compose/.env.sample index e14bde27728..8ca7ca008fb 100644 --- a/infra/docker-compose/.env.sample +++ b/infra/docker-compose/.env.sample @@ -1,19 +1,23 @@ +# General COMPOSE_PROJECT_NAME=feast - FEAST_VERSION=latest +FEAST_REPOSITORY_VERSION=v0.4.7 +# Feast Core FEAST_CORE_IMAGE=gcr.io/kf-feast/feast-core -FEAST_CORE_CONFIG=direct-runner -FEAST_CORE_GCP_SERVICE_ACCOUNT_KEY=placeholder +FEAST_CORE_CONFIG=direct-runner.yml +FEAST_CORE_GCP_SERVICE_ACCOUNT_KEY=placeholder.json -FEAST_SERVING_IMAGE=gcr.io/kf-feast/feast-serving -FEAST_ONLINE_SERVING_CONFIG=online-serving -FEAST_ONLINE_STORE_CONFIG=redis-store -FEAST_BATCH_SERVING_CONFIG=batch-serving -FEAST_BATCH_STORE_CONFIG=bq-store -FEAST_BATCH_SERVING_GCP_SERVICE_ACCOUNT_KEY=placeholder -FEAST_JOB_STAGING_LOCATION=gs://your-gcp-project/bucket +# Feast Serving Batch (BigQuery) +FEAST_BATCH_SERVING_CONFIG=batch-serving.yml +FEAST_BATCH_STORE_CONFIG=bq-store.yml +FEAST_BATCH_SERVING_GCP_SERVICE_ACCOUNT_KEY=placeholder.json +FEAST_BATCH_JOB_STAGING_LOCATION=gs://your-gcs-bucket/staging -FEAST_JUPYTER_IMAGE=gcr.io/kf-feast/feast-jupyter -FEAST_JUPYTER_GCP_SERVICE_ACCOUNT_KEY=placeholder +# Feast Serving Online (Redis) +FEAST_SERVING_IMAGE=gcr.io/kf-feast/feast-serving +FEAST_ONLINE_SERVING_CONFIG=online-serving.yml +FEAST_ONLINE_STORE_CONFIG=redis-store.yml +# Jupyter +FEAST_JUPYTER_GCP_SERVICE_ACCOUNT_KEY=placeholder.json \ No newline at end of file diff --git a/infra/docker-compose/docker-compose.batch.yml b/infra/docker-compose/docker-compose.batch.yml index c00ac9475bd..247dd0b6719 100644 --- a/infra/docker-compose/docker-compose.batch.yml +++ b/infra/docker-compose/docker-compose.batch.yml @@ -4,22 +4,26 @@ services: batch-serving: image: ${FEAST_SERVING_IMAGE}:${FEAST_VERSION} volumes: - - ./serving/${FEAST_BATCH_SERVING_CONFIG}.yml:/etc/feast/application.yml - - ./serving/${FEAST_BATCH_STORE_CONFIG}.yml:/etc/feast/store.yml - - ./gcp-service-accounts/${FEAST_BATCH_SERVING_GCP_SERVICE_ACCOUNT_KEY}.json:/etc/gcloud/service-accounts/key.json + - ./serving/${FEAST_BATCH_SERVING_CONFIG}:/etc/feast/application.yml + - ./serving/${FEAST_BATCH_STORE_CONFIG}:/etc/feast/store.yml + - ./gcp-service-accounts/${FEAST_BATCH_SERVING_GCP_SERVICE_ACCOUNT_KEY}:/etc/gcloud/service-accounts/key.json depends_on: - - core - redis ports: - 6567:6567 restart: on-failure environment: GOOGLE_APPLICATION_CREDENTIALS: /etc/gcloud/service-accounts/key.json - FEAST_JOB_STAGING_LOCATION: ${FEAST_JOB_STAGING_LOCATION} + FEAST_JOB_STAGING_LOCATION: ${FEAST_BATCH_JOB_STAGING_LOCATION} command: - "java" - "-Xms1024m" - "-Xmx1024m" - "-jar" - "/opt/feast/feast-serving.jar" - - "--spring.config.location=classpath:/application.yml,file:/etc/feast/application.yml" \ No newline at end of file + - "--spring.config.location=classpath:/application.yml,file:/etc/feast/application.yml" + + redis: + image: redis:5-alpine + ports: + - "6379:6379" \ No newline at end of file diff --git a/infra/docker-compose/docker-compose.dev.yml b/infra/docker-compose/docker-compose.dev.yml new file mode 100644 index 00000000000..840c6dd2673 --- /dev/null +++ b/infra/docker-compose/docker-compose.dev.yml @@ -0,0 +1,70 @@ +version: "3.7" + +services: + core: + image: maven:3.6-openjdk-11 + volumes: + - ${HOME}/.m2:/root/.m2:delegated + - ../../.:/code:cached + environment: + DB_HOST: db + FEAST_STREAM_OPTIONS_BOOTSTRAPSERVERS: kafka:9092 + GOOGLE_APPLICATION_CREDENTIALS: /etc/gcloud/service-accounts/key.json + restart: on-failure + depends_on: + - db + - kafka + ports: + - 6565:6565 + + working_dir: /code + command: + - mvn + - -pl + - core + - spring-boot:run + + jupyter: + image: jupyter/minimal-notebook:619e9cc2fc07 + volumes: + - ./gcp-service-accounts/${FEAST_JUPYTER_GCP_SERVICE_ACCOUNT_KEY}:/etc/gcloud/service-accounts/key.json + - ./jupyter/startup.sh:/etc/startup.sh + depends_on: + - core + environment: + FEAST_CORE_URL: core:6565 + FEAST_ONLINE_SERVING_URL: online-serving:6566 + FEAST_BATCH_SERVING_URL: batch-serving:6567 + GOOGLE_APPLICATION_CREDENTIALS: /etc/gcloud/service-accounts/key.json + FEAST_REPOSITORY_VERSION: ${FEAST_REPOSITORY_VERSION} + ports: + - 8888:8888 + command: ["/etc/startup.sh"] + + kafka: + image: confluentinc/cp-kafka:5.2.1 + environment: + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + KAFKA_ADVERTISED_LISTENERS: INSIDE://kafka:9092,OUTSIDE://localhost:9094 + KAFKA_LISTENERS: INSIDE://:9092,OUTSIDE://:9094 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT + KAFKA_INTER_BROKER_LISTENER_NAME: INSIDE + ports: + - "9092:9092" + - "9094:9094" + + depends_on: + - zookeeper + + zookeeper: + image: confluentinc/cp-zookeeper:5.2.1 + environment: + ZOOKEEPER_CLIENT_PORT: 2181 + + db: + image: postgres:12-alpine + environment: + POSTGRES_PASSWORD: password + ports: + - "5432:5432" diff --git a/infra/docker-compose/docker-compose.online.yml b/infra/docker-compose/docker-compose.online.yml new file mode 100644 index 00000000000..ed96f0e0963 --- /dev/null +++ b/infra/docker-compose/docker-compose.online.yml @@ -0,0 +1,23 @@ +version: "3.7" + +services: + online-serving: + image: ${FEAST_SERVING_IMAGE}:${FEAST_VERSION} + volumes: + - ./serving/${FEAST_ONLINE_SERVING_CONFIG}:/etc/feast/application.yml + - ./serving/${FEAST_ONLINE_STORE_CONFIG}:/etc/feast/store.yml + depends_on: + - redis + ports: + - 6566:6566 + restart: on-failure + command: + - java + - -jar + - /opt/feast/feast-serving.jar + - --spring.config.location=classpath:/application.yml,file:/etc/feast/application.yml + + redis: + image: redis:5-alpine + ports: + - "6379:6379" \ No newline at end of file diff --git a/infra/docker-compose/docker-compose.yml b/infra/docker-compose/docker-compose.yml index a224500ca0a..1dda766608e 100644 --- a/infra/docker-compose/docker-compose.yml +++ b/infra/docker-compose/docker-compose.yml @@ -4,8 +4,8 @@ services: core: image: ${FEAST_CORE_IMAGE}:${FEAST_VERSION} volumes: - - ./core/${FEAST_CORE_CONFIG}.yml:/etc/feast/application.yml - - ./gcp-service-accounts/${FEAST_CORE_GCP_SERVICE_ACCOUNT_KEY}.json:/etc/gcloud/service-accounts/key.json + - ./core/${FEAST_CORE_CONFIG}:/etc/feast/application.yml + - ./gcp-service-accounts/${FEAST_CORE_GCP_SERVICE_ACCOUNT_KEY}:/etc/gcloud/service-accounts/key.json environment: DB_HOST: db GOOGLE_APPLICATION_CREDENTIALS: /etc/gcloud/service-accounts/key.json @@ -19,46 +19,24 @@ services: - java - -jar - /opt/feast/feast-core.jar - - --spring.config.location=classpath:/application.yml,file:/etc/feast/application.yaml - - online-serving: - image: ${FEAST_SERVING_IMAGE}:${FEAST_VERSION} - volumes: - - ./serving/${FEAST_ONLINE_SERVING_CONFIG}.yml:/etc/feast/application.yml - - ./serving/${FEAST_ONLINE_STORE_CONFIG}.yml:/etc/feast/store.yml - depends_on: - - core - - redis - ports: - - 6566:6566 - restart: on-failure - command: - - java - - -jar - - /opt/feast/feast-serving.jar - --spring.config.location=classpath:/application.yml,file:/etc/feast/application.yml jupyter: - image: ${FEAST_JUPYTER_IMAGE}:${FEAST_VERSION} + image: jupyter/minimal-notebook:619e9cc2fc07 volumes: - - ./jupyter/notebooks:/home/jovyan/feast-notebooks - - ./jupyter/features:/home/jovyan/features - - ./gcp-service-accounts/${FEAST_JUPYTER_GCP_SERVICE_ACCOUNT_KEY}.json:/etc/gcloud/service-accounts/key.json + - ./gcp-service-accounts/${FEAST_JUPYTER_GCP_SERVICE_ACCOUNT_KEY}:/etc/gcloud/service-accounts/key.json + - ./jupyter/startup.sh:/etc/startup.sh depends_on: - core - - online-serving environment: FEAST_CORE_URL: core:6565 - FEAST_SERVING_URL: online-serving:6566 + FEAST_ONLINE_SERVING_URL: online-serving:6566 + FEAST_BATCH_SERVING_URL: batch-serving:6567 GOOGLE_APPLICATION_CREDENTIALS: /etc/gcloud/service-accounts/key.json + FEAST_REPOSITORY_VERSION: ${FEAST_REPOSITORY_VERSION} ports: - 8888:8888 - command: - - start-notebook.sh - - --NotebookApp.token='' - - redis: - image: redis:5-alpine + command: ["/etc/startup.sh"] kafka: image: confluentinc/cp-kafka:5.2.1 @@ -70,7 +48,8 @@ services: KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT KAFKA_INTER_BROKER_LISTENER_NAME: INSIDE ports: - - 9094:9092 + - "9092:9092" + - "9094:9094" depends_on: - zookeeper @@ -81,4 +60,8 @@ services: ZOOKEEPER_CLIENT_PORT: 2181 db: - image: postgres:12-alpine \ No newline at end of file + image: postgres:12-alpine + environment: + POSTGRES_PASSWORD: password + ports: + - "5432:5432" \ No newline at end of file diff --git a/infra/docker-compose/gcp-service-accounts/placeholder.json b/infra/docker-compose/gcp-service-accounts/placeholder.json index 9e26dfeeb6e..5609d6923cf 100644 --- a/infra/docker-compose/gcp-service-accounts/placeholder.json +++ b/infra/docker-compose/gcp-service-accounts/placeholder.json @@ -1 +1,4 @@ -{} \ No newline at end of file +{ + "type": "service_account", + "project_id": "just-some-project" +} \ No newline at end of file diff --git a/infra/docker-compose/jupyter/features/cust_trans_fs.yaml b/infra/docker-compose/jupyter/features/cust_trans_fs.yaml deleted file mode 100644 index eb21ce9b35b..00000000000 --- a/infra/docker-compose/jupyter/features/cust_trans_fs.yaml +++ /dev/null @@ -1,11 +0,0 @@ -name: customer_transactions -kind: feature_set -entities: -- name: customer_id - valueType: INT64 -features: -- name: daily_transactions - valueType: FLOAT -- name: total_transactions - valueType: FLOAT -maxAge: 3600s \ No newline at end of file diff --git a/infra/docker-compose/jupyter/features/cust_trans_fs_updated.yaml b/infra/docker-compose/jupyter/features/cust_trans_fs_updated.yaml deleted file mode 100644 index 8293d04b881..00000000000 --- a/infra/docker-compose/jupyter/features/cust_trans_fs_updated.yaml +++ /dev/null @@ -1,13 +0,0 @@ -name: customer_transactions -kind: feature_set -entities: -- name: customer_id - valueType: INT64 -features: -- name: daily_transactions - valueType: FLOAT -- name: total_transactions - valueType: FLOAT -- name: discounts - valueType: FLOAT -maxAge: 3600s \ No newline at end of file diff --git a/infra/docker-compose/jupyter/notebooks/feast-batch-serving.ipynb b/infra/docker-compose/jupyter/notebooks/feast-batch-serving.ipynb deleted file mode 100644 index c288093f07b..00000000000 --- a/infra/docker-compose/jupyter/notebooks/feast-batch-serving.ipynb +++ /dev/null @@ -1,504 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Feast Batch Serving\n", - "This is an extension to `feast-quickstart` notebook to demonstrate the batch serving capability of Feast.\n", - "\n", - "## Prerequisite\n", - "- A running Feast Serving service with store configuration that supports batch retrieval. (eg. BigQuery store)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Data Preparation\n" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import feast\n", - "import numpy as np\n", - "import pandas as pd\n", - "from datetime import datetime, timedelta\n", - "from feast.serving.ServingService_pb2 import GetOnlineFeaturesRequest\n", - "from feast.types.Value_pb2 import Value as Value\n", - "from feast.client import Client\n", - "from feast.feature_set import FeatureSet" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "client = feast.Client(core_url=\"core:6565\", serving_url=\"batch-serving:6567\")" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "cust_trans_fs = FeatureSet.from_yaml(\"../features/cust_trans_fs.yaml\")" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Feature set updated/created: \"customer_transactions:1\".\n" - ] - } - ], - "source": [ - "client.apply(cust_trans_fs)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
datetimecustomer_iddaily_transactionstotal_transactions
02019-12-06 02:17:46.899904100002.797627175.978266
12019-12-06 02:17:46.899915100014.931632153.871975
22019-12-06 02:17:46.899922100020.206628108.558844
32019-12-06 02:17:46.899929100032.354937119.549455
42019-12-06 02:17:46.899937100047.171423115.345183
\n", - "
" - ], - "text/plain": [ - " datetime customer_id daily_transactions \\\n", - "0 2019-12-06 02:17:46.899904 10000 2.797627 \n", - "1 2019-12-06 02:17:46.899915 10001 4.931632 \n", - "2 2019-12-06 02:17:46.899922 10002 0.206628 \n", - "3 2019-12-06 02:17:46.899929 10003 2.354937 \n", - "4 2019-12-06 02:17:46.899937 10004 7.171423 \n", - "\n", - " total_transactions \n", - "0 175.978266 \n", - "1 153.871975 \n", - "2 108.558844 \n", - "3 119.549455 \n", - "4 115.345183 " - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "offset = 10000\n", - "nr_of_customers = 5\n", - "customer_df = pd.DataFrame(\n", - " {\n", - " \"datetime\": [datetime.utcnow() for _ in range(nr_of_customers)],\n", - " \"customer_id\": [offset + inc for inc in range(nr_of_customers)],\n", - " \"daily_transactions\": [np.random.uniform(0, 10) for _ in range(nr_of_customers)],\n", - " \"total_transactions\": [np.random.uniform(100, 200) for _ in range(nr_of_customers)],\n", - " }\n", - ")\n", - "customer_df" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "100%|██████████| 5/5 [00:00<00:00, 7.24rows/s]" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Ingested 5 rows into customer_transactions:1\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\n" - ] - } - ], - "source": [ - "client.ingest(cust_trans_fs, dataframe=customer_df)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "## Batch Retrieval\n", - "Batch retrieval takes a dataframe containing the entities column and event timestamp as an input. The result would be the outer join of the input and the features. The input dataframe needs to have a column named `datetime` as event timestamp. No results will be returned if the difference between the feature ingestion timestamp and the `event_timestamp` is greater than the `maxAge` parameter specified in the feature set." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
customer_transactions_v1_feature_timestampcustomer_idevent_timestampcustomer_transactions_v1_daily_transactionscustomer_transactions_v1_total_transactions
02019-12-06 02:17:46+00:00100012019-12-06 02:17:55.612449+00:004.931632153.871980
12019-12-06 02:17:46+00:00100042019-12-06 02:17:55.612449+00:007.171423115.345184
22019-12-06 02:17:46+00:00100002019-12-06 02:17:55.612449+00:002.797627175.978270
32019-12-06 02:17:46+00:00100022019-12-06 02:17:55.612449+00:000.206628108.558846
42019-12-06 02:17:46+00:00100032019-12-06 02:17:55.612449+00:002.354937119.549450
\n", - "
" - ], - "text/plain": [ - " customer_transactions_v1_feature_timestamp customer_id \\\n", - "0 2019-12-06 02:17:46+00:00 10001 \n", - "1 2019-12-06 02:17:46+00:00 10004 \n", - "2 2019-12-06 02:17:46+00:00 10000 \n", - "3 2019-12-06 02:17:46+00:00 10002 \n", - "4 2019-12-06 02:17:46+00:00 10003 \n", - "\n", - " event_timestamp \\\n", - "0 2019-12-06 02:17:55.612449+00:00 \n", - "1 2019-12-06 02:17:55.612449+00:00 \n", - "2 2019-12-06 02:17:55.612449+00:00 \n", - "3 2019-12-06 02:17:55.612449+00:00 \n", - "4 2019-12-06 02:17:55.612449+00:00 \n", - "\n", - " customer_transactions_v1_daily_transactions \\\n", - "0 4.931632 \n", - "1 7.171423 \n", - "2 2.797627 \n", - "3 0.206628 \n", - "4 2.354937 \n", - "\n", - " customer_transactions_v1_total_transactions \n", - "0 153.871980 \n", - "1 115.345184 \n", - "2 175.978270 \n", - "3 108.558846 \n", - "4 119.549450 " - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "entity_df = customer_df[[\"customer_id\"]].assign(datetime=datetime.utcnow())\n", - "feature_ids=[\n", - " \"customer_transactions:1:daily_transactions\",\n", - " \"customer_transactions:1:total_transactions\",\n", - "]\n", - "batch_job = client.get_batch_features(feature_ids, entity_df)\n", - "batch_job.to_dataframe()" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
customer_transactions_v1_feature_timestampcustomer_idevent_timestampcustomer_transactions_v1_daily_transactionscustomer_transactions_v1_total_transactions
0None100002020-01-05 02:18:43.900732+00:00NoneNone
1None100012020-01-05 02:18:43.900732+00:00NoneNone
2None100022020-01-05 02:18:43.900732+00:00NoneNone
3None100032020-01-05 02:18:43.900732+00:00NoneNone
4None100042020-01-05 02:18:43.900732+00:00NoneNone
\n", - "
" - ], - "text/plain": [ - " customer_transactions_v1_feature_timestamp customer_id \\\n", - "0 None 10000 \n", - "1 None 10001 \n", - "2 None 10002 \n", - "3 None 10003 \n", - "4 None 10004 \n", - "\n", - " event_timestamp \\\n", - "0 2020-01-05 02:18:43.900732+00:00 \n", - "1 2020-01-05 02:18:43.900732+00:00 \n", - "2 2020-01-05 02:18:43.900732+00:00 \n", - "3 2020-01-05 02:18:43.900732+00:00 \n", - "4 2020-01-05 02:18:43.900732+00:00 \n", - "\n", - " customer_transactions_v1_daily_transactions \\\n", - "0 None \n", - "1 None \n", - "2 None \n", - "3 None \n", - "4 None \n", - "\n", - " customer_transactions_v1_total_transactions \n", - "0 None \n", - "1 None \n", - "2 None \n", - "3 None \n", - "4 None " - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "stale_entity_df = customer_df[[\"customer_id\"]].assign(datetime=datetime.utcnow() + timedelta(days=30))\n", - "feature_ids=[\n", - " \"customer_transactions:1:daily_transactions\",\n", - " \"customer_transactions:1:total_transactions\",\n", - "]\n", - "batch_job = client.get_batch_features(feature_ids, stale_entity_df)\n", - "batch_job.to_dataframe()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.3" - }, - "pycharm": { - "stem_cell": { - "cell_type": "raw", - "metadata": { - "collapsed": false - }, - "source": [] - } - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} diff --git a/infra/docker-compose/jupyter/notebooks/feast-quickstart.ipynb b/infra/docker-compose/jupyter/notebooks/feast-quickstart.ipynb deleted file mode 100644 index b89e59b1e49..00000000000 --- a/infra/docker-compose/jupyter/notebooks/feast-quickstart.ipynb +++ /dev/null @@ -1,569 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Feast Quick Start\n", - "This is a quick example to demonstrate:\n", - "- Register a feature set on Feast\n", - "- Ingest features into Feast\n", - "- Retrieve the ingested features from Feast\n", - "- Update a feature" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import feast\n", - "import numpy as np\n", - "import pandas as pd\n", - "from datetime import datetime\n", - "from feast.serving.ServingService_pb2 import GetOnlineFeaturesRequest\n", - "from feast.types.Value_pb2 import Value as Value\n", - "from feast.client import Client\n", - "from feast.feature_set import FeatureSet" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "First, instantiate the client.\n", - "Feast endpoints can be set via the following environmental variables: `FEAST_CORE_URL`, `FEAST_SERVING_URL`.\n", - "Alternatively, they can also be passed in explicitly as follows:\n", - " \n", - "`client = feast.Client(core_url=core:6565, serving_url=online-serving:6566)`" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "client = feast.Client()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Register a feature set\n", - "\n", - "Let's create and register our first feature set. Below is an example of a basic customer transactions feature set that has been exported to YAML:\n", - "```\n", - "name: customer_transactions\n", - "kind: feature_set\n", - "entities:\n", - "- name: customer_id\n", - " valueType: INT64\n", - "features:\n", - "- name: daily_transactions\n", - " valueType: FLOAT\n", - "- name: total_transactions\n", - " valueType: FLOAT\n", - "maxAge: 3600s \n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "cust_trans_fs = FeatureSet.from_yaml(\"../features/cust_trans_fs.yaml\")" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Feature set updated/created: \"customer_transactions:1\".\n" - ] - } - ], - "source": [ - "client.apply(cust_trans_fs)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Ingest features into Feast\n", - "The dataframe below contains the features and entities of the above feature set." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
datetimecustomer_iddaily_transactionstotal_transactions
02019-11-26 12:03:47.320634100005.178112110.670651
12019-11-26 12:03:47.320644100010.268114195.393913
22019-11-26 12:03:47.320651100021.486614136.929052
32019-11-26 12:03:47.320658100039.676433166.022999
42019-11-26 12:03:47.320665100045.928573165.687951
\n", - "
" - ], - "text/plain": [ - " datetime customer_id daily_transactions \\\n", - "0 2019-11-26 12:03:47.320634 10000 5.178112 \n", - "1 2019-11-26 12:03:47.320644 10001 0.268114 \n", - "2 2019-11-26 12:03:47.320651 10002 1.486614 \n", - "3 2019-11-26 12:03:47.320658 10003 9.676433 \n", - "4 2019-11-26 12:03:47.320665 10004 5.928573 \n", - "\n", - " total_transactions \n", - "0 110.670651 \n", - "1 195.393913 \n", - "2 136.929052 \n", - "3 166.022999 \n", - "4 165.687951 " - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "offset = 10000\n", - "nr_of_customers = 5\n", - "customer_df = pd.DataFrame(\n", - " {\n", - " \"datetime\": [datetime.utcnow() for _ in range(nr_of_customers)],\n", - " \"customer_id\": [offset + inc for inc in range(nr_of_customers)],\n", - " \"daily_transactions\": [np.random.uniform(0, 10) for _ in range(nr_of_customers)],\n", - " \"total_transactions\": [np.random.uniform(100, 200) for _ in range(nr_of_customers)],\n", - " }\n", - ")\n", - "customer_df" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - " 0%| | 0/5 [00:00\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
datetimecustomer_iddaily_transactionstotal_transactionsdiscounts
02019-11-26 12:03:47.320634100005.178112110.6706518.389938
12019-11-26 12:03:47.320644100010.268114195.3939130.430047
22019-11-26 12:03:47.320651100021.486614136.9290527.408917
32019-11-26 12:03:47.320658100039.676433166.0229991.192721
42019-11-26 12:03:47.320665100045.928573165.6879512.051037
\n", - "" - ], - "text/plain": [ - " datetime customer_id daily_transactions \\\n", - "0 2019-11-26 12:03:47.320634 10000 5.178112 \n", - "1 2019-11-26 12:03:47.320644 10001 0.268114 \n", - "2 2019-11-26 12:03:47.320651 10002 1.486614 \n", - "3 2019-11-26 12:03:47.320658 10003 9.676433 \n", - "4 2019-11-26 12:03:47.320665 10004 5.928573 \n", - "\n", - " total_transactions discounts \n", - "0 110.670651 8.389938 \n", - "1 195.393913 0.430047 \n", - "2 136.929052 7.408917 \n", - "3 166.022999 1.192721 \n", - "4 165.687951 2.051037 " - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "discounts = [np.random.uniform(0, 10) for _ in range(nr_of_customers)]\n", - "customer_df_updated = customer_df.assign(discounts=discounts)\n", - "customer_df_updated" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - " 0%| | 0/5 [00:00 /var/log/redis.install.log + +mkdir 7000 7001 7002 7003 7004 7005 +for i in {0..5} ; do +echo "port 700$i +cluster-enabled yes +cluster-config-file nodes-$i.conf +cluster-node-timeout 5000 +appendonly yes" > 700$i/redis.conf +redis-server 700$i/redis.conf --daemonize yes +done +echo yes | redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 \ +127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \ +--cluster-replicas 1 diff --git a/.prow/scripts/sync-helm-charts.sh b/infra/scripts/sync-helm-charts.sh similarity index 100% rename from .prow/scripts/sync-helm-charts.sh rename to infra/scripts/sync-helm-charts.sh diff --git a/.prow/scripts/test-core-ingestion.sh b/infra/scripts/test-core-ingestion.sh similarity index 87% rename from .prow/scripts/test-core-ingestion.sh rename to infra/scripts/test-core-ingestion.sh index 98a47ca68c9..d3b42926d83 100755 --- a/.prow/scripts/test-core-ingestion.sh +++ b/infra/scripts/test-core-ingestion.sh @@ -1,6 +1,11 @@ #!/usr/bin/env bash -.prow/scripts/download-maven-cache.sh \ +apt-get -qq update +apt-get -y install build-essential + +make lint-java + +infra/scripts/download-maven-cache.sh \ --archive-uri gs://feast-templocation-kf-feast/.m2.2019-10-24.tar \ --output-dir /root/ diff --git a/infra/scripts/test-docker-compose.sh b/infra/scripts/test-docker-compose.sh new file mode 100755 index 00000000000..40a85900c2f --- /dev/null +++ b/infra/scripts/test-docker-compose.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +set -e + +echo " +============================================================ +Running Docker Compose tests with pytest at 'tests/e2e' +============================================================ +" + +export PROJECT_ROOT_DIR=$(git rev-parse --show-toplevel) +export COMPOSE_INTERACTIVE_NO_CLI=1 + +# Create Docker Compose configuration file +cd ${PROJECT_ROOT_DIR}/infra/docker-compose/ +cp .env.sample .env + +# Start Docker Compose containers +docker-compose -f docker-compose.yml -f docker-compose.online.yml up -d + +# Get Jupyter container IP address +export JUPYTER_DOCKER_CONTAINER_IP_ADDRESS=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' feast_jupyter_1) + +# Print Jupyter container information +docker inspect feast_jupyter_1 +docker logs feast_jupyter_1 + +# Wait for Jupyter Notebook Container to come online +${PROJECT_ROOT_DIR}/infra/scripts/wait-for-it.sh ${JUPYTER_DOCKER_CONTAINER_IP_ADDRESS}:8888 --timeout=300 + +# Run e2e tests for Redis +docker exec feast_jupyter_1 bash -c 'cd feast/tests/e2e/ && pytest -s basic-ingest-redis-serving.py --core_url core:6565 --serving_url=online-serving:6566' + +# Shut down docker-compose images +docker-compose -f docker-compose.yml -f docker-compose.online.yml down \ No newline at end of file diff --git a/infra/scripts/test-end-to-end-batch-dataflow.sh b/infra/scripts/test-end-to-end-batch-dataflow.sh new file mode 100755 index 00000000000..9b3c8b5a83b --- /dev/null +++ b/infra/scripts/test-end-to-end-batch-dataflow.sh @@ -0,0 +1,247 @@ +#!/usr/bin/env bash +echo "Preparing environment variables..." + +set -e +set -o pipefail + +test -z ${GOOGLE_APPLICATION_CREDENTIALS} && GOOGLE_APPLICATION_CREDENTIALS="/etc/service-account/service-account-df.json" +test -z ${GCLOUD_PROJECT} && GCLOUD_PROJECT="kf-feast" +test -z ${GCLOUD_REGION} && GCLOUD_REGION="us-central1" +test -z ${GCLOUD_NETWORK} && GCLOUD_NETWORK="default" +test -z ${GCLOUD_SUBNET} && GCLOUD_SUBNET="default" +test -z ${TEMP_BUCKET} && TEMP_BUCKET="feast-templocation-kf-feast" +test -z ${K8_CLUSTER_NAME} && K8_CLUSTER_NAME="feast-e2e-dataflow" +test -z ${HELM_RELEASE_NAME} && HELM_RELEASE_NAME="feast-e2e-release" + +echo " +This script will run end-to-end tests for Feast Core and Batch Serving using Dataflow Runner. + +1. Install gcloud SDK and required packages. +2. Create temporary BQ table for Feast Serving. +3. Generate valid names for IP addresses and k8s cluster, then store as environment variables. +4. Create GKE nodepool for Feast e2e test with DataflowRunner. +5. Setup Feast Core, Feast Serving and dependencies using Helm. + - Redis as the job store for Feast Batch Serving. + - Postgres for persisting Feast metadata. + - Kafka and Zookeeper as the Source in Feast. +6. Install Python 3.7.4, Feast Python SDK and run end-to-end tests from + tests/e2e via pytest. +7. Tear down infrastructure, including Dataflow jobs. +" + +ORIGINAL_DIR=$(pwd) +echo $ORIGINAL_DIR + +echo " +============================================================ +Installing gcloud SDK and required packages +============================================================ +" +apt-get -qq update +apt-get -y install wget netcat kafkacat build-essential gettext-base curl kubectl +curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 +chmod 700 $ORIGINAL_DIR/get_helm.sh +$ORIGINAL_DIR/get_helm.sh + +if [[ ! $(command -v gsutil) ]]; then + CURRENT_DIR=$(dirname "$BASH_SOURCE") + . "${CURRENT_DIR}"/install-google-cloud-sdk.sh +fi + +export GOOGLE_APPLICATION_CREDENTIALS +gcloud auth activate-service-account --key-file ${GOOGLE_APPLICATION_CREDENTIALS} + +gcloud config set project ${GCLOUD_PROJECT} +gcloud config set compute/region ${GCLOUD_REGION} +gcloud config list + +echo " +============================================================ +Creating temp BQ table for Feast Serving +============================================================ +" +DATASET_NAME=feast_e2e_$(date +%s) + +bq --location=US --project_id=${GCLOUD_PROJECT} mk \ + --dataset \ + --default_table_expiration 86400 \ + ${GCLOUD_PROJECT}:${DATASET_NAME} + +echo " +============================================================ +Check and generate valid k8s cluster name +============================================================ +" +count=0 +for cluster_name in $(gcloud container clusters list --format "list(name)") +do + if [[ $cluster_name == "feast-e2e-dataflow"* ]]; then + count=$((count + 1)) + fi +done +temp="$K8_CLUSTER_NAME-$count" +export K8_CLUSTER_NAME=$temp +echo "Cluster name is $K8_CLUSTER_NAME" + +echo " +============================================================ +Reserving IP addresses for Feast dependencies +============================================================ +" +feast_kafka_1_ip_name="feast-kafka-$((count*3 + 1))" +feast_kafka_2_ip_name="feast-kafka-$((count*3 + 2))" +feast_kafka_3_ip_name="feast-kafka-$((count*3 + 3))" +feast_redis_ip_name="feast-redis-$((count + 1))" +feast_statsd_ip_name="feast-statsd-$((count + 1))" +gcloud compute addresses create \ + $feast_kafka_1_ip_name $feast_kafka_2_ip_name $feast_kafka_3_ip_name $feast_redis_ip_name $feast_statsd_ip_name \ + --region ${GCLOUD_REGION} --subnet ${GCLOUD_SUBNET} + +ip_count=0 +for ip_addr_name in $feast_kafka_1_ip_name $feast_kafka_2_ip_name $feast_kafka_3_ip_name $feast_redis_ip_name $feast_statsd_ip_name +do + if [[ "$ip_count" == 0 ]]; then + export feast_kafka_1_ip=$(gcloud compute addresses describe ${ip_addr_name} --region=${GCLOUD_REGION} --format "value(address)") + elif [[ "$ip_count" == 1 ]]; then + export feast_kafka_2_ip=$(gcloud compute addresses describe ${ip_addr_name} --region=${GCLOUD_REGION} --format "value(address)") + elif [[ "$ip_count" == 2 ]]; then + export feast_kafka_3_ip=$(gcloud compute addresses describe ${ip_addr_name} --region=${GCLOUD_REGION} --format "value(address)") + elif [[ "$ip_count" == 3 ]]; then + export feast_redis_ip=$(gcloud compute addresses describe ${ip_addr_name} --region=${GCLOUD_REGION} --format "value(address)") + elif [[ "$ip_count" == 4 ]]; then + export feast_statsd_ip=$(gcloud compute addresses describe ${ip_addr_name} --region=${GCLOUD_REGION} --format "value(address)") + fi + ip_count=$((ip_count + 1)) + export "$(echo $ip_addr_name | tr '-' '_')=$(gcloud compute addresses describe ${ip_addr_name} --region=${GCLOUD_REGION} --format "value(address)")" +done + +echo " +============================================================ +Creating GKE nodepool for Feast e2e test with DataflowRunner +============================================================ +" +gcloud container clusters create ${K8_CLUSTER_NAME} --region ${GCLOUD_REGION} \ + --enable-cloud-logging \ + --enable-cloud-monitoring \ + --network ${GCLOUD_NETWORK} \ + --subnetwork ${GCLOUD_SUBNET} \ + --machine-type n1-standard-2 +sleep 120 + +echo " +============================================================ +Create feast-postgres-database Secret in GKE nodepool +============================================================ +" +kubectl create secret generic feast-postgresql --from-literal=postgresql-password=password + +echo " +============================================================ +Create feast-gcp-service-account Secret in GKE nodepool +============================================================ +" +cd $ORIGINAL_DIR/infra/scripts +kubectl create secret generic feast-gcp-service-account --from-file=${GOOGLE_APPLICATION_CREDENTIALS} + +echo " +============================================================ +Export required environment variables +============================================================ +" +export TEMP_BUCKET=$TEMP_BUCKET +export DATASET_NAME=$DATASET_NAME +export GCLOUD_PROJECT=$GCLOUD_PROJECT +export GCLOUD_NETWORK=$GCLOUD_NETWORK +export GCLOUD_SUBNET=$GCLOUD_SUBNET +export GCLOUD_REGION=$GCLOUD_REGION + +echo " +============================================================ +Helm install Feast and its dependencies +============================================================ +" +cd $ORIGINAL_DIR/infra/scripts/test-templates +envsubst $'$TEMP_BUCKET $DATASET_NAME $GCLOUD_PROJECT $GCLOUD_NETWORK \ + $GCLOUD_SUBNET $GCLOUD_REGION $feast_kafka_1_ip + $feast_kafka_2_ip $feast_kafka_3_ip $feast_redis_ip $feast_statsd_ip' < values-end-to-end-batch-dataflow.yaml > $ORIGINAL_DIR/infra/charts/feast/values-end-to-end-batch-dataflow-updated.yaml + +cd $ORIGINAL_DIR/infra/charts +helm template feast + +cd $ORIGINAL_DIR/infra/charts/feast +helm install --wait --timeout 600s --values="values-end-to-end-batch-dataflow-updated.yaml" ${HELM_RELEASE_NAME} . + +echo " +============================================================ +Installing Python 3.7 with Miniconda and Feast SDK +============================================================ +" +# Install Python 3.7 with Miniconda +wget -q https://repo.continuum.io/miniconda/Miniconda3-4.7.12-Linux-x86_64.sh \ + -O /tmp/miniconda.sh +bash /tmp/miniconda.sh -b -p /root/miniconda -f +/root/miniconda/bin/conda init +source ~/.bashrc + +# Install Feast Python SDK and test requirements +cd $ORIGINAL_DIR +make compile-protos-python +pip install -qe sdk/python +pip install -qr tests/e2e/requirements.txt + +echo " +============================================================ +Running end-to-end tests with pytest at 'tests/e2e' +============================================================ +" +# Default artifact location setting in Prow jobs +LOGS_ARTIFACT_PATH=/logs/artifacts + +cd $ORIGINAL_DIR/tests/e2e + +core_ip=$(kubectl get -o jsonpath="{.spec.clusterIP}" service ${HELM_RELEASE_NAME}-feast-core) +serving_ip=$(kubectl get -o jsonpath="{.spec.clusterIP}" service ${HELM_RELEASE_NAME}-feast-batch-serving) + +set +e +pytest bq-batch-retrieval.py -m dataflow_runner --core_url "$core_ip:6565" --serving_url "$serving_ip:6566" --gcs_path "gs://${TEMP_BUCKET}/" --junitxml=${LOGS_ARTIFACT_PATH}/python-sdk-test-report.xml +TEST_EXIT_CODE=$? + +if [[ ${TEST_EXIT_CODE} != 0 ]]; then + echo "[DEBUG] Printing logs" + ls -ltrh /var/log/feast* + cat /var/log/feast-serving-warehouse.log /var/log/feast-core.log + + echo "[DEBUG] Printing Python packages list" + pip list +fi + +cd ${ORIGINAL_DIR} + +echo " +============================================================ +Cleaning up +============================================================ +" +cd $ORIGINAL_DIR/tests/e2e + +# Remove BQ Dataset +bq rm -r -f ${GCLOUD_PROJECT}:${DATASET_NAME} + +# Uninstall helm release before clearing PVCs +helm uninstall ${HELM_RELEASE_NAME} +kubectl delete pvc --all + +# Release IP addresses +yes | gcloud compute addresses delete $feast_kafka_1_ip_name $feast_kafka_2_ip_name $feast_kafka_3_ip_name $feast_redis_ip_name $feast_statsd_ip_name --region=${GCLOUD_REGION} + +# Tear down GKE infrastructure +gcloud container clusters delete --region=${GCLOUD_REGION} ${K8_CLUSTER_NAME} + +# Stop Dataflow jobs from retrieved Dataflow job ids in ingesting_jobs.txt +while read line +do + echo $line + gcloud dataflow jobs cancel $line --region=${GCLOUD_REGION} +done < ingesting_jobs.txt + +exit ${TEST_EXIT_CODE} diff --git a/.prow/scripts/test-end-to-end-batch.sh b/infra/scripts/test-end-to-end-batch.sh similarity index 57% rename from .prow/scripts/test-end-to-end-batch.sh rename to infra/scripts/test-end-to-end-batch.sh index 5ff947f20f5..ffaba183a9d 100755 --- a/.prow/scripts/test-end-to-end-batch.sh +++ b/infra/scripts/test-end-to-end-batch.sh @@ -3,11 +3,29 @@ set -e set -o pipefail -if ! cat /etc/*release | grep -q stretch; then - echo ${BASH_SOURCE} only supports Debian stretch. - echo Please change your operating system to use this script. - exit 1 -fi +PYTEST_MARK='direct_runner' #default + +print_usage() { + printf "Usage: ./test-end-to-end-batch -m pytest_mark" +} + +while getopts 'm:' flag; do + case "${flag}" in + m) PYTEST_MARK="${OPTARG}" ;; + *) print_usage + exit 1 ;; + esac +done + +test -z ${GOOGLE_APPLICATION_CREDENTIALS} && GOOGLE_APPLICATION_CREDENTIALS="/etc/service-account/service-account.json" +test -z ${SKIP_BUILD_JARS} && SKIP_BUILD_JARS="false" +test -z ${GOOGLE_CLOUD_PROJECT} && GOOGLE_CLOUD_PROJECT="kf-feast" +test -z ${TEMP_BUCKET} && TEMP_BUCKET="feast-templocation-kf-feast" +test -z ${JOBS_STAGING_LOCATION} && JOBS_STAGING_LOCATION="gs://${TEMP_BUCKET}/staging-location" + +# Get the current build version using maven (and pom.xml) +FEAST_BUILD_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) +echo Building version: $FEAST_BUILD_VERSION echo " This script will run end-to-end tests for Feast Core and Batch Serving. @@ -16,10 +34,13 @@ This script will run end-to-end tests for Feast Core and Batch Serving. 2. Install Redis as the job store for Feast Batch Serving. 4. Install Postgres for persisting Feast metadata. 5. Install Kafka and Zookeeper as the Source in Feast. -6. Install Python 3.7.4, Feast Python SDK and run end-to-end tests from +6. Install Python 3.7.4, Feast Python SDK and run end-to-end tests from tests/e2e via pytest. " +apt-get -qq update +apt-get -y install wget netcat kafkacat build-essential + echo " ============================================================ @@ -31,8 +52,8 @@ if [[ ! $(command -v gsutil) ]]; then . "${CURRENT_DIR}"/install-google-cloud-sdk.sh fi -export GOOGLE_APPLICATION_CREDENTIALS=/etc/service-account/service-account.json -gcloud auth activate-service-account --key-file /etc/service-account/service-account.json +export GOOGLE_APPLICATION_CREDENTIALS +gcloud auth activate-service-account --key-file ${GOOGLE_APPLICATION_CREDENTIALS} @@ -41,10 +62,9 @@ echo " Installing Redis at localhost:6379 ============================================================ " -apt-get -qq update # Allow starting serving in this Maven Docker image. Default set to not allowed. echo "exit 0" > /usr/sbin/policy-rc.d -apt-get -y install redis-server wget > /var/log/redis.install.log +apt-get -y install redis-server > /var/log/redis.install.log redis-server --daemonize yes redis-cli ping @@ -73,24 +93,32 @@ Installing Kafka at localhost:9092 wget -qO- https://www-eu.apache.org/dist/kafka/2.3.0/kafka_2.12-2.3.0.tgz | tar xz mv kafka_2.12-2.3.0/ /tmp/kafka nohup /tmp/kafka/bin/zookeeper-server-start.sh /tmp/kafka/config/zookeeper.properties &> /var/log/zookeeper.log 2>&1 & -sleep 10 +sleep 5 tail -n10 /var/log/zookeeper.log nohup /tmp/kafka/bin/kafka-server-start.sh /tmp/kafka/config/server.properties &> /var/log/kafka.log 2>&1 & -sleep 30 +sleep 20 tail -n10 /var/log/kafka.log - -echo " -============================================================ -Building jars for Feast -============================================================ -" - -.prow/scripts/download-maven-cache.sh \ - --archive-uri gs://feast-templocation-kf-feast/.m2.2019-10-24.tar \ - --output-dir /root/ - -# Build jars for Feast -mvn --quiet --batch-mode --define skipTests=true clean package +kafkacat -b localhost:9092 -L + +if [[ ${SKIP_BUILD_JARS} != "true" ]]; then + echo " + ============================================================ + Building jars for Feast + ============================================================ + " + + infra/scripts/download-maven-cache.sh \ + --archive-uri gs://feast-templocation-kf-feast/.m2.2019-10-24.tar \ + --output-dir /root/ + + # Build jars for Feast + mvn --quiet --batch-mode --define skipTests=true clean package + + ls -lh core/target/*jar + ls -lh serving/target/*jar +else + echo "[DEBUG] Skipping building jars" +fi echo " ============================================================ @@ -104,12 +132,17 @@ grpc: enable-reflection: true feast: - version: 0.3 jobs: - runner: DirectRunner - options: {} - updates: - timeoutSeconds: 240 + polling_interval_milliseconds: 10000 + job_update_timeout_seconds: 240 + + active_runner: direct + + runners: + - name: direct + type: DirectRunner + options: {} + metrics: enabled: false @@ -125,28 +158,24 @@ spring: jpa: properties.hibernate: format_sql: true - event.merge.entity_copy_observer: allow + event: + merge: + entity_copy_observer: allow hibernate.naming.physical-strategy=org.hibernate.boot.model.naming: PhysicalNamingStrategyStandardImpl hibernate.ddl-auto: update datasource: url: jdbc:postgresql://localhost:5432/postgres username: postgres password: password - -management: - metrics: - export: - simple: - enabled: false - statsd: - enabled: false EOF -nohup java -jar core/target/feast-core-*-SNAPSHOT.jar \ +nohup java -jar core/target/feast-core-${FEAST_BUILD_VERSION}.jar \ --spring.config.location=file:///tmp/core.application.yml \ &> /var/log/feast-core.log & sleep 35 tail -n10 /var/log/feast-core.log +nc -w2 localhost 6565 < /dev/null + echo " ============================================================ Starting Feast Warehouse Serving @@ -155,52 +184,60 @@ Starting Feast Warehouse Serving DATASET_NAME=feast_$(date +%s) -bq --location=US --project_id=kf-feast mk \ +bq --location=US --project_id=${GOOGLE_CLOUD_PROJECT} mk \ --dataset \ --default_table_expiration 86400 \ - kf-feast:$DATASET_NAME + ${GOOGLE_CLOUD_PROJECT}:${DATASET_NAME} # Start Feast Online Serving in background -cat < /tmp/serving.store.bigquery.yml -name: warehouse -type: BIGQUERY -bigquery_config: - projectId: kf-feast - datasetId: $DATASET_NAME -subscriptions: - - name: "*" - version: "*" - project: "*" -EOF - cat < /tmp/serving.warehouse.application.yml feast: - version: 0.3 + # GRPC service address for Feast Core + # Feast Serving requires connection to Feast Core to retrieve and reload Feast metadata (e.g. FeatureSpecs, Store information) core-host: localhost core-grpc-port: 6565 + + # Indicates the active store. Only a single store in the last can be active at one time. In the future this key + # will be deprecated in order to allow multiple stores to be served from a single serving instance + active_store: historical + + # List of store configurations + stores: + - name: historical + type: BIGQUERY + config: + project_id: ${GOOGLE_CLOUD_PROJECT} + dataset_id: ${DATASET_NAME} + staging_location: ${JOBS_STAGING_LOCATION} + initial_retry_delay_seconds: 1 + total_timeout_seconds: 21600 + subscriptions: + - name: "*" + project: "*" + version: "*" + + job_store: + redis_host: localhost + redis_port: 6379 + tracing: enabled: false - store: - config-path: /tmp/serving.store.bigquery.yml - jobs: - staging-location: gs://feast-templocation-kf-feast/staging-location - store-type: REDIS - store-options: - host: $REMOTE_HOST - port: 6379 + grpc: port: 6566 enable-reflection: true -spring: - main: - web-environment: false + +server: + port: 8081 + EOF -nohup java -jar serving/target/feast-serving-*-SNAPSHOT.jar \ +nohup java -jar serving/target/feast-serving-${FEAST_BUILD_VERSION}.jar \ --spring.config.location=file:///tmp/serving.warehouse.application.yml \ &> /var/log/feast-serving-warehouse.log & sleep 15 tail -n100 /var/log/feast-serving-warehouse.log +nc -w2 localhost 6566 < /dev/null echo " ============================================================ @@ -215,6 +252,7 @@ bash /tmp/miniconda.sh -b -p /root/miniconda -f source ~/.bashrc # Install Feast Python SDK and test requirements +make compile-protos-python pip install -qe sdk/python pip install -qr tests/e2e/requirements.txt @@ -230,11 +268,19 @@ ORIGINAL_DIR=$(pwd) cd tests/e2e set +e -pytest bq-batch-retrieval.py --junitxml=${LOGS_ARTIFACT_PATH}/python-sdk-test-report.xml +pytest bq-batch-retrieval.py -m ${PYTEST_MARK} --gcs_path "gs://${TEMP_BUCKET}/" --junitxml=${LOGS_ARTIFACT_PATH}/python-sdk-test-report.xml TEST_EXIT_CODE=$? +if [[ ${TEST_EXIT_CODE} != 0 ]]; then + echo "[DEBUG] Printing logs" + ls -ltrh /var/log/feast* + cat /var/log/feast-serving-warehouse.log /var/log/feast-core.log + + echo "[DEBUG] Printing Python packages list" + pip list +fi + cd ${ORIGINAL_DIR} -exit ${TEST_EXIT_CODE} echo " ============================================================ @@ -242,4 +288,6 @@ Cleaning up ============================================================ " -bq rm -r -f kf-feast:$DATASET_NAME +bq rm -r -f ${GOOGLE_CLOUD_PROJECT}:${DATASET_NAME} + +exit ${TEST_EXIT_CODE} \ No newline at end of file diff --git a/infra/scripts/test-end-to-end-redis-cluster.sh b/infra/scripts/test-end-to-end-redis-cluster.sh new file mode 100755 index 00000000000..de41edf4741 --- /dev/null +++ b/infra/scripts/test-end-to-end-redis-cluster.sh @@ -0,0 +1,265 @@ +#!/usr/bin/env bash + +set -e +set -o pipefail + +test -z ${GOOGLE_APPLICATION_CREDENTIALS} && GOOGLE_APPLICATION_CREDENTIALS="/etc/service-account/service-account.json" +test -z ${SKIP_BUILD_JARS} && SKIP_BUILD_JARS="false" +test -z ${GOOGLE_CLOUD_PROJECT} && GOOGLE_CLOUD_PROJECT="kf-feast" +test -z ${TEMP_BUCKET} && TEMP_BUCKET="feast-templocation-kf-feast" +test -z ${JOBS_STAGING_LOCATION} && JOBS_STAGING_LOCATION="gs://${TEMP_BUCKET}/staging-location" + +# Get the current build version using maven (and pom.xml) +FEAST_BUILD_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) +echo Building version: $FEAST_BUILD_VERSION + +echo " +This script will run end-to-end tests for Feast Core and Online Serving. + +1. Install Redis as the store for Feast Online Serving. +2. Install Postgres for persisting Feast metadata. +3. Install Kafka and Zookeeper as the Source in Feast. +4. Install Python 3.7.4, Feast Python SDK and run end-to-end tests from + tests/e2e via pytest. +" + +apt-get -qq update +apt-get -y install wget netcat kafkacat build-essential + +echo " +============================================================ +Installing Redis at localhost:6379 +============================================================ +" +# Allow starting serving in this Maven Docker image. Default set to not allowed. +echo "exit 0" > /usr/sbin/policy-rc.d +infra/scripts/setup-redis-cluster.sh +redis-cli -c -p 7000 ping + +echo " +============================================================ +Installing Postgres at localhost:5432 +============================================================ +" +apt-get -y install postgresql > /var/log/postgresql.install.log +service postgresql start +# Initialize with database: 'postgres', user: 'postgres', password: 'password' +cat < /tmp/update-postgres-role.sh +psql -c "ALTER USER postgres PASSWORD 'password';" +EOF +chmod +x /tmp/update-postgres-role.sh +su -s /bin/bash -c /tmp/update-postgres-role.sh postgres +export PGPASSWORD=password +pg_isready + +echo " +============================================================ +Installing Zookeeper at localhost:2181 +Installing Kafka at localhost:9092 +============================================================ +" +wget -qO- https://www-eu.apache.org/dist/kafka/2.3.0/kafka_2.12-2.3.0.tgz | tar xz +mv kafka_2.12-2.3.0/ /tmp/kafka +nohup /tmp/kafka/bin/zookeeper-server-start.sh /tmp/kafka/config/zookeeper.properties &> /var/log/zookeeper.log 2>&1 & +sleep 5 +tail -n10 /var/log/zookeeper.log +nohup /tmp/kafka/bin/kafka-server-start.sh /tmp/kafka/config/server.properties &> /var/log/kafka.log 2>&1 & +sleep 20 +tail -n10 /var/log/kafka.log +kafkacat -b localhost:9092 -L + +if [[ ${SKIP_BUILD_JARS} != "true" ]]; then +echo " +============================================================ +Building jars for Feast +============================================================ +" + +infra/scripts/download-maven-cache.sh \ + --archive-uri gs://feast-templocation-kf-feast/.m2.2019-10-24.tar \ + --output-dir /root/ + +# Build jars for Feast +mvn --quiet --batch-mode --define skipTests=true clean package + +ls -lh core/target/*jar +ls -lh serving/target/*jar +else + echo "[DEBUG] Skipping building jars" +fi + +echo " +============================================================ +Starting Feast Core +============================================================ +" +# Start Feast Core in background +cat < /tmp/core.application.yml +grpc: + port: 6565 + enable-reflection: true + +feast: + jobs: + polling_interval_milliseconds: 30000 + job_update_timeout_seconds: 240 + + active_runner: direct + + runners: + - name: direct + type: DirectRunner + options: {} + + metrics: + enabled: false + + stream: + type: kafka + options: + topic: feast-features + bootstrapServers: localhost:9092 + replicationFactor: 1 + partitions: 1 + +spring: + jpa: + properties.hibernate: + format_sql: true + event.merge.entity_copy_observer: allow + hibernate.naming.physical-strategy=org.hibernate.boot.model.naming: PhysicalNamingStrategyStandardImpl + hibernate.ddl-auto: update + datasource: + url: jdbc:postgresql://localhost:5432/postgres + username: postgres + password: password + +management: + metrics: + export: + simple: + enabled: false + statsd: + enabled: false +EOF + +nohup java -jar core/target/feast-core-${FEAST_BUILD_VERSION}.jar \ + --spring.config.location=file:///tmp/core.application.yml \ + &> /var/log/feast-core.log & +sleep 35 +tail -n10 /var/log/feast-core.log +nc -w2 localhost 6565 < /dev/null + +echo " +============================================================ +Starting Feast Online Serving +============================================================ +" +# Start Feast Online Serving in background +cat < /tmp/serving.store.redis.cluster.yml +name: serving +type: REDIS_CLUSTER +redis_cluster_config: + nodes: + - host: localhost + port: 7000 + - host: localhost + port: 7001 + - host: localhost + port: 7002 + - host: localhost + port: 7003 + - host: localhost + port: 7004 + - host: localhost + port: 7005 +subscriptions: + - name: "*" + version: "*" + project: "*" +EOF + +cat < /tmp/serving.online.application.yml +feast: + core-host: localhost + core-grpc-port: 6565 + + active_store: online + + # List of store configurations + stores: + - name: online # Name of the store (referenced by active_store) + type: REDIS_CLUSTER # Type of the store. REDIS, BIGQUERY are available options + config: + # Connection string specifies the IP and ports of Redis instances in Redis cluster + connection_string: "localhost:7000,localhost:7001,localhost:7002,localhost:7003,localhost:7004,localhost:7005" + # Subscriptions indicate which feature sets needs to be retrieved and used to populate this store + subscriptions: + # Wildcards match all options. No filtering is done. + - name: "*" + project: "*" + version: "*" + + tracing: + enabled: false + +grpc: + port: 6566 + enable-reflection: true + +spring: + main: + web-environment: false + +EOF + +nohup java -jar serving/target/feast-serving-${FEAST_BUILD_VERSION}.jar \ + --spring.config.location=file:///tmp/serving.online.application.yml \ + &> /var/log/feast-serving-online.log & +sleep 15 +tail -n100 /var/log/feast-serving-online.log +nc -w2 localhost 6566 < /dev/null + +echo " +============================================================ +Installing Python 3.7 with Miniconda and Feast SDK +============================================================ +" +# Install Python 3.7 with Miniconda +wget -q https://repo.continuum.io/miniconda/Miniconda3-4.7.12-Linux-x86_64.sh \ + -O /tmp/miniconda.sh +bash /tmp/miniconda.sh -b -p /root/miniconda -f +/root/miniconda/bin/conda init +source ~/.bashrc + +# Install Feast Python SDK and test requirements +make compile-protos-python +pip install -qe sdk/python +pip install -qr tests/e2e/requirements.txt + +echo " +============================================================ +Running end-to-end tests with pytest at 'tests/e2e' +============================================================ +" +# Default artifact location setting in Prow jobs +LOGS_ARTIFACT_PATH=/logs/artifacts + +ORIGINAL_DIR=$(pwd) +cd tests/e2e + +set +e +pytest basic-ingest-redis-serving.py --junitxml=${LOGS_ARTIFACT_PATH}/python-sdk-test-report.xml +TEST_EXIT_CODE=$? + +if [[ ${TEST_EXIT_CODE} != 0 ]]; then + echo "[DEBUG] Printing logs" + ls -ltrh /var/log/feast* + cat /var/log/feast-serving-online.log /var/log/feast-core.log + + echo "[DEBUG] Printing Python packages list" + pip list +fi + +cd ${ORIGINAL_DIR} +exit ${TEST_EXIT_CODE} diff --git a/.prow/scripts/test-end-to-end.sh b/infra/scripts/test-end-to-end.sh similarity index 73% rename from .prow/scripts/test-end-to-end.sh rename to infra/scripts/test-end-to-end.sh index cc65968ca22..452d646f2f2 100755 --- a/.prow/scripts/test-end-to-end.sh +++ b/infra/scripts/test-end-to-end.sh @@ -3,11 +3,15 @@ set -e set -o pipefail -if ! cat /etc/*release | grep -q stretch; then - echo ${BASH_SOURCE} only supports Debian stretch. - echo Please change your operating system to use this script. - exit 1 -fi +test -z ${GOOGLE_APPLICATION_CREDENTIALS} && GOOGLE_APPLICATION_CREDENTIALS="/etc/service-account/service-account.json" +test -z ${SKIP_BUILD_JARS} && SKIP_BUILD_JARS="false" +test -z ${GOOGLE_CLOUD_PROJECT} && GOOGLE_CLOUD_PROJECT="kf-feast" +test -z ${TEMP_BUCKET} && TEMP_BUCKET="feast-templocation-kf-feast" +test -z ${JOBS_STAGING_LOCATION} && JOBS_STAGING_LOCATION="gs://${TEMP_BUCKET}/staging-location" + +# Get the current build version using maven (and pom.xml) +FEAST_BUILD_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) +echo Building version: $FEAST_BUILD_VERSION echo " This script will run end-to-end tests for Feast Core and Online Serving. @@ -15,19 +19,18 @@ This script will run end-to-end tests for Feast Core and Online Serving. 1. Install Redis as the store for Feast Online Serving. 2. Install Postgres for persisting Feast metadata. 3. Install Kafka and Zookeeper as the Source in Feast. -4. Install Python 3.7.4, Feast Python SDK and run end-to-end tests from +4. Install Python 3.7.4, Feast Python SDK and run end-to-end tests from tests/e2e via pytest. " apt-get -qq update -apt-get -y install wget netcat kafkacat +apt-get -y install wget netcat kafkacat build-essential echo " ============================================================ Installing Redis at localhost:6379 ============================================================ " - # Allow starting serving in this Maven Docker image. Default set to not allowed. echo "exit 0" > /usr/sbin/policy-rc.d apt-get -y install redis-server > /var/log/redis.install.log @@ -66,13 +69,14 @@ sleep 20 tail -n10 /var/log/kafka.log kafkacat -b localhost:9092 -L +if [[ ${SKIP_BUILD_JARS} != "true" ]]; then echo " ============================================================ Building jars for Feast ============================================================ " -.prow/scripts/download-maven-cache.sh \ +infra/scripts/download-maven-cache.sh \ --archive-uri gs://feast-templocation-kf-feast/.m2.2019-10-24.tar \ --output-dir /root/ @@ -81,6 +85,9 @@ mvn --quiet --batch-mode --define skipTests=true clean package ls -lh core/target/*jar ls -lh serving/target/*jar +else + echo "[DEBUG] Skipping building jars" +fi echo " ============================================================ @@ -94,12 +101,17 @@ grpc: enable-reflection: true feast: - version: 0.3 jobs: - runner: DirectRunner - options: {} - updates: - timeoutSeconds: 240 + polling_interval_milliseconds: 30000 + job_update_timeout_seconds: 240 + + active_runner: direct + + runners: + - name: direct + type: DirectRunner + options: {} + metrics: enabled: false @@ -115,25 +127,19 @@ spring: jpa: properties.hibernate: format_sql: true - event.merge.entity_copy_observer: allow + event: + merge: + entity_copy_observer: allow hibernate.naming.physical-strategy=org.hibernate.boot.model.naming: PhysicalNamingStrategyStandardImpl hibernate.ddl-auto: update - datasource: url: jdbc:postgresql://localhost:5432/postgres username: postgres password: password -management: - metrics: - export: - simple: - enabled: false - statsd: - enabled: false EOF -nohup java -jar core/target/feast-core-*-SNAPSHOT.jar \ +nohup java -jar core/target/feast-core-$FEAST_BUILD_VERSION.jar \ --spring.config.location=file:///tmp/core.application.yml \ &> /var/log/feast-core.log & sleep 35 @@ -145,53 +151,43 @@ echo " Starting Feast Online Serving ============================================================ " -# Start Feast Online Serving in background -cat < /tmp/serving.store.redis.yml -name: serving -type: REDIS -redis_config: - host: localhost - port: 6379 -subscriptions: - - name: "*" - version: "*" - project: "*" -EOF cat < /tmp/serving.online.application.yml feast: - version: 0.3 core-host: localhost core-grpc-port: 6565 - tracing: - enabled: false + active_store: serving - store: - config-path: /tmp/serving.store.redis.yml - redis-pool-max-size: 128 - redis-pool-max-idle: 16 + # List of store configurations + stores: + - name: serving + type: REDIS # Type of the store. REDIS, BIGQUERY are available options + config: + host: localhost + port: 6379 + subscriptions: + - name: "*" + project: "*" + version: "*" - jobs: - staging-location: gs://feast-templocation-kf-feast/staging-location - store-type: - store-options: {} + tracing: + enabled: false grpc: port: 6566 enable-reflection: true -spring: - main: - web-environment: false +server: + port: 8081 EOF -nohup java -jar serving/target/feast-serving-*-SNAPSHOT.jar \ +nohup java -jar serving/target/feast-serving-${FEAST_BUILD_VERSION}.jar \ --spring.config.location=file:///tmp/serving.online.application.yml \ &> /var/log/feast-serving-online.log & sleep 15 -tail -n10 /var/log/feast-serving-online.log +tail -n100 /var/log/feast-serving-online.log nc -w2 localhost 6566 < /dev/null echo " @@ -207,6 +203,7 @@ bash /tmp/miniconda.sh -b -p /root/miniconda -f source ~/.bashrc # Install Feast Python SDK and test requirements +make compile-protos-python pip install -qe sdk/python pip install -qr tests/e2e/requirements.txt @@ -225,5 +222,14 @@ set +e pytest basic-ingest-redis-serving.py --junitxml=${LOGS_ARTIFACT_PATH}/python-sdk-test-report.xml TEST_EXIT_CODE=$? +if [[ ${TEST_EXIT_CODE} != 0 ]]; then + echo "[DEBUG] Printing logs" + ls -ltrh /var/log/feast* + cat /var/log/feast-serving-online.log /var/log/feast-core.log + + echo "[DEBUG] Printing Python packages list" + pip list +fi + cd ${ORIGINAL_DIR} exit ${TEST_EXIT_CODE} diff --git a/.prow/scripts/test-golang-sdk.sh b/infra/scripts/test-golang-sdk.sh similarity index 96% rename from .prow/scripts/test-golang-sdk.sh rename to infra/scripts/test-golang-sdk.sh index b586927a512..666f6c12d0c 100755 --- a/.prow/scripts/test-golang-sdk.sh +++ b/infra/scripts/test-golang-sdk.sh @@ -2,6 +2,8 @@ set -o pipefail +make lint-go + cd sdk/go go test -v 2>&1 | tee /tmp/test_output TEST_EXIT_CODE=$? diff --git a/.prow/scripts/test-java-sdk.sh b/infra/scripts/test-java-sdk.sh similarity index 100% rename from .prow/scripts/test-java-sdk.sh rename to infra/scripts/test-java-sdk.sh diff --git a/.prow/scripts/test-python-sdk.sh b/infra/scripts/test-python-sdk.sh similarity index 65% rename from .prow/scripts/test-python-sdk.sh rename to infra/scripts/test-python-sdk.sh index e7e1cc24874..7c264eed642 100755 --- a/.prow/scripts/test-python-sdk.sh +++ b/infra/scripts/test-python-sdk.sh @@ -5,7 +5,10 @@ set -e # Default artifact location setting in Prow jobs LOGS_ARTIFACT_PATH=/logs/artifacts -cd sdk/python -pip install -r requirements-ci.txt +pip install -r sdk/python/requirements-ci.txt +make compile-protos-python +make lint-python + +cd sdk/python/ pip install -e . pytest --junitxml=${LOGS_ARTIFACT_PATH}/python-sdk-test-report.xml diff --git a/.prow/scripts/test-serving.sh b/infra/scripts/test-serving.sh similarity index 90% rename from .prow/scripts/test-serving.sh rename to infra/scripts/test-serving.sh index b56001619b3..ce9dc0a8162 100755 --- a/.prow/scripts/test-serving.sh +++ b/infra/scripts/test-serving.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -.prow/scripts/download-maven-cache.sh \ +infra/scripts/download-maven-cache.sh \ --archive-uri gs://feast-templocation-kf-feast/.m2.2019-10-24.tar \ --output-dir /root/ diff --git a/infra/scripts/test-templates/values-end-to-end-batch-dataflow.yaml b/infra/scripts/test-templates/values-end-to-end-batch-dataflow.yaml new file mode 100644 index 00000000000..aff9b0d6b71 --- /dev/null +++ b/infra/scripts/test-templates/values-end-to-end-batch-dataflow.yaml @@ -0,0 +1,141 @@ +feast-core: + # feast-core.enabled -- Flag to install Feast Core + enabled: true + gcpServiceAccount: + enabled: true + postgresql: + existingSecret: feast-postgresql + image: + tag: $PULL_PULL_SHA:1 + application-override.yaml: + feast: + stream: + options: + bootstrapServers: $feast_kafka_1_ip:31090 + jobs: + active_runner: dataflow + + runners: + - name: dataflow + type: DataflowRunner + options: + project: $GCLOUD_PROJECT + region: $GCLOUD_REGION + zone: $GCLOUD_REGION-a + tempLocation: gs://$TEMP_BUCKET/tempLocation + network: $GCLOUD_NETWORK + subnetwork: regions/$GCLOUD_REGION/subnetworks/$GCLOUD_SUBNET + maxNumWorkers: 1 + autoscalingAlgorithm: THROUGHPUT_BASED + usePublicIps: false + workerMachineType: n1-standard-1 + deadLetterTableSpec: $GCLOUD_PROJECT:$DATASET_NAME.deadletter + + metrics: + enabled: true + host: $feast_statsd_ip + +feast-online-serving: + # feast-online-serving.enabled -- Flag to install Feast Online Serving + enabled: true + image: + tag: $PULL_PULL_SHA:1 + application-override.yaml: + feast: + active_store: online + + # List of store configurations + stores: + - name: online + type: REDIS + config: + host: $feast_redis_ip + port: 6379 + subscriptions: + - name: "*" + project: "*" + version: "*" + +feast-batch-serving: + # feast-batch-serving.enabled -- Flag to install Feast Batch Serving + enabled: true + image: + tag: $PULL_PULL_SHA:1 + gcpServiceAccount: + enabled: true + + application-override.yaml: + feast: + active_store: historical + + # List of store configurations + stores: + - name: historical + type: BIGQUERY + config: + project_id: $GCLOUD_PROJECT + dataset_id: $DATASET_NAME + staging_location: gs://$TEMP_BUCKET/stagingLocation + initial_retry_delay_seconds: 3 + total_timeout_seconds: 21600 + subscriptions: + - name: "*" + project: "*" + version: "*" + +postgresql: + # postgresql.enabled -- Flag to install Postgresql + enabled: true + existingSecret: feast-postgresql + +kafka: + # kafka.enabled -- Flag to install Kafka + enabled: true + external: + enabled: true + type: LoadBalancer + annotations: + cloud.google.com/load-balancer-type: Internal + loadBalancerSourceRanges: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + firstListenerPort: 31090 + loadBalancerIP: + - $feast_kafka_1_ip + - $feast_kafka_2_ip + - $feast_kafka_3_ip + configurationOverrides: + "advertised.listeners": |- + EXTERNAL://${LOAD_BALANCER_IP}:31090 + "listener.security.protocol.map": |- + PLAINTEXT:PLAINTEXT,EXTERNAL:PLAINTEXT + "log.retention.hours": 1 + +redis: + # redis.enabled -- Flag to install Redis + enabled: true + usePassword: false + master: + service: + type: LoadBalancer + loadBalancerIP: $feast_redis_ip + annotations: + cloud.google.com/load-balancer-type: Internal + loadBalancerSourceRanges: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + +prometheus-statsd-exporter: + # prometheus-statsd-exporter.enabled -- Flag to install StatsD to Prometheus Exporter + enabled: true + service: + type: LoadBalancer + annotations: + cloud.google.com/load-balancer-type: Internal + loadBalancerSourceRanges: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + loadBalancerIP: $feast_statsd_ip diff --git a/infra/scripts/wait-for-it.sh b/infra/scripts/wait-for-it.sh new file mode 100755 index 00000000000..51942ce6dc4 --- /dev/null +++ b/infra/scripts/wait-for-it.sh @@ -0,0 +1,183 @@ +#!/usr/bin/env bash +# Use this script to test if a given TCP host/port are available +# Source: https://github.com/vishnubob/wait-for-it + +WAITFORIT_cmdname=${0##*/} + +echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } + +usage() +{ + cat << USAGE >&2 +Usage: + $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args] + -h HOST | --host=HOST Host or IP under test + -p PORT | --port=PORT TCP port under test + Alternatively, you specify the host and port as host:port + -s | --strict Only execute subcommand if the test succeeds + -q | --quiet Don't output any status messages + -t TIMEOUT | --timeout=TIMEOUT + Timeout in seconds, zero for no timeout + -- COMMAND ARGS Execute command with args after the test finishes +USAGE + exit 1 +} + +wait_for() +{ + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + else + echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout" + fi + WAITFORIT_start_ts=$(date +%s) + while : + do + if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then + nc -z $WAITFORIT_HOST $WAITFORIT_PORT + WAITFORIT_result=$? + else + (echo > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1 + WAITFORIT_result=$? + fi + if [[ $WAITFORIT_result -eq 0 ]]; then + WAITFORIT_end_ts=$(date +%s) + echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds" + break + fi + sleep 1 + done + return $WAITFORIT_result +} + +wait_for_wrapper() +{ + # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 + if [[ $WAITFORIT_QUIET -eq 1 ]]; then + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + else + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + fi + WAITFORIT_PID=$! + trap "kill -INT -$WAITFORIT_PID" INT + wait $WAITFORIT_PID + WAITFORIT_RESULT=$? + if [[ $WAITFORIT_RESULT -ne 0 ]]; then + echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + fi + return $WAITFORIT_RESULT +} + +# process arguments +while [[ $# -gt 0 ]] +do + case "$1" in + *:* ) + WAITFORIT_hostport=(${1//:/ }) + WAITFORIT_HOST=${WAITFORIT_hostport[0]} + WAITFORIT_PORT=${WAITFORIT_hostport[1]} + shift 1 + ;; + --child) + WAITFORIT_CHILD=1 + shift 1 + ;; + -q | --quiet) + WAITFORIT_QUIET=1 + shift 1 + ;; + -s | --strict) + WAITFORIT_STRICT=1 + shift 1 + ;; + -h) + WAITFORIT_HOST="$2" + if [[ $WAITFORIT_HOST == "" ]]; then break; fi + shift 2 + ;; + --host=*) + WAITFORIT_HOST="${1#*=}" + shift 1 + ;; + -p) + WAITFORIT_PORT="$2" + if [[ $WAITFORIT_PORT == "" ]]; then break; fi + shift 2 + ;; + --port=*) + WAITFORIT_PORT="${1#*=}" + shift 1 + ;; + -t) + WAITFORIT_TIMEOUT="$2" + if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi + shift 2 + ;; + --timeout=*) + WAITFORIT_TIMEOUT="${1#*=}" + shift 1 + ;; + --) + shift + WAITFORIT_CLI=("$@") + break + ;; + --help) + usage + ;; + *) + echoerr "Unknown argument: $1" + usage + ;; + esac +done + +if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then + echoerr "Error: you need to provide a host and port to test." + usage +fi + +WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15} +WAITFORIT_STRICT=${WAITFORIT_STRICT:-0} +WAITFORIT_CHILD=${WAITFORIT_CHILD:-0} +WAITFORIT_QUIET=${WAITFORIT_QUIET:-0} + +# Check to see if timeout is from busybox? +WAITFORIT_TIMEOUT_PATH=$(type -p timeout) +WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH) + +WAITFORIT_BUSYTIMEFLAG="" +if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then + WAITFORIT_ISBUSY=1 + # Check if busybox timeout uses -t flag + # (recent Alpine versions don't support -t anymore) + if timeout &>/dev/stdout | grep -q -e '-t '; then + WAITFORIT_BUSYTIMEFLAG="-t" + fi +else + WAITFORIT_ISBUSY=0 +fi + +if [[ $WAITFORIT_CHILD -gt 0 ]]; then + wait_for + WAITFORIT_RESULT=$? + exit $WAITFORIT_RESULT +else + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + wait_for_wrapper + WAITFORIT_RESULT=$? + else + wait_for + WAITFORIT_RESULT=$? + fi +fi + +if [[ $WAITFORIT_CLI != "" ]]; then + if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then + echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess" + exit $WAITFORIT_RESULT + fi + exec "${WAITFORIT_CLI[@]}" +else + exit $WAITFORIT_RESULT +fi \ No newline at end of file diff --git a/ingestion/pom.xml b/ingestion/pom.xml index 4908b546985..a62d4202ee0 100644 --- a/ingestion/pom.xml +++ b/ingestion/pom.xml @@ -32,9 +32,14 @@ - org.xolstice.maven.plugins - protobuf-maven-plugin + org.apache.maven.plugins + maven-compiler-plugin + + + 8 + + org.apache.maven.plugins maven-shade-plugin @@ -65,18 +70,10 @@ org.springframework org.springframework.vendor - - io.grpc - io.grpc.vendor - io.opencensus io.opencensus.vendor - - feast.core - feast.core.vendor - com.google.cloud.bigquery com.google.cloud.bigquery.vendor @@ -86,26 +83,43 @@ + + + org.jacoco + jacoco-maven-plugin + - org.glassfish - javax.el - 3.0.0 + dev.feast + datatypes-java + ${project.version} - javax.validation - validation-api - 2.0.1.Final + dev.feast + feast-storage-api + ${project.version} - org.hibernate.validator - hibernate-validator - 6.0.13.Final + dev.feast + feast-storage-connector-redis + ${project.version} + + + + dev.feast + feast-storage-connector-redis-cluster + ${project.version} + + + + dev.feast + feast-storage-connector-bigquery + ${project.version} @@ -120,15 +134,6 @@ provided - - io.grpc - grpc-stub - - - - com.google.cloud - google-cloud-storage - com.google.cloud google-cloud-bigquery @@ -148,27 +153,6 @@ mockito-core - - com.fasterxml.jackson.core - jackson-annotations - - - com.fasterxml.jackson.core - jackson-core - - - com.fasterxml.jackson.core - jackson-databind - - - com.fasterxml.jackson.dataformat - jackson-dataformat-yaml - - - com.fasterxml.jackson.module - jackson-module-jsonSchema - - com.google.protobuf protobuf-java @@ -214,8 +198,8 @@ - redis.clients - jedis + io.lettuce + lettuce-core @@ -223,15 +207,6 @@ slf4j-api - - - - ch.qos.logback - logback-classic - 1.2.3 - runtime - - com.github.kstyrc @@ -255,5 +230,12 @@ 2.8.1 + + + org.apache.commons + commons-math3 + 3.6.1 + + diff --git a/ingestion/src/main/java/feast/ingestion/ImportJob.java b/ingestion/src/main/java/feast/ingestion/ImportJob.java index 41af5f9bb40..16efe11f55f 100644 --- a/ingestion/src/main/java/feast/ingestion/ImportJob.java +++ b/ingestion/src/main/java/feast/ingestion/ImportJob.java @@ -17,22 +17,28 @@ package feast.ingestion; import static feast.ingestion.utils.SpecUtil.getFeatureSetReference; +import static feast.ingestion.utils.StoreUtil.getFeatureSink; import com.google.protobuf.InvalidProtocolBufferException; -import feast.core.FeatureSetProto.FeatureSet; -import feast.core.SourceProto.Source; -import feast.core.StoreProto.Store; +import feast.ingestion.options.BZip2Decompressor; import feast.ingestion.options.ImportOptions; +import feast.ingestion.options.StringListStreamConverter; +import feast.ingestion.transform.ProcessAndValidateFeatureRows; import feast.ingestion.transform.ReadFromSource; -import feast.ingestion.transform.ValidateFeatureRows; -import feast.ingestion.transform.WriteFailedElementToBigQuery; -import feast.ingestion.transform.WriteToStore; -import feast.ingestion.transform.metrics.WriteMetricsTransform; -import feast.ingestion.utils.ResourceUtil; +import feast.ingestion.transform.metrics.WriteFailureMetricsTransform; +import feast.ingestion.transform.metrics.WriteSuccessMetricsTransform; import feast.ingestion.utils.SpecUtil; -import feast.ingestion.utils.StoreUtil; -import feast.ingestion.values.FailedElement; -import feast.types.FeatureRowProto.FeatureRow; +import feast.proto.core.FeatureSetProto.FeatureSet; +import feast.proto.core.FeatureSetProto.FeatureSetSpec; +import feast.proto.core.SourceProto.Source; +import feast.proto.core.StoreProto.Store; +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.storage.api.writer.DeadletterSink; +import feast.storage.api.writer.FailedElement; +import feast.storage.api.writer.FeatureSink; +import feast.storage.api.writer.WriteResult; +import feast.storage.connectors.bigquery.writer.BigQueryDeadletterSink; +import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -57,15 +63,14 @@ public class ImportJob { * @param args arguments to be passed to Beam pipeline * @throws InvalidProtocolBufferException if options passed to the pipeline are invalid */ - public static void main(String[] args) throws InvalidProtocolBufferException { + public static void main(String[] args) throws IOException { ImportOptions options = PipelineOptionsFactory.fromArgs(args).withValidation().create().as(ImportOptions.class); runPipeline(options); } @SuppressWarnings("UnusedReturnValue") - public static PipelineResult runPipeline(ImportOptions options) - throws InvalidProtocolBufferException { + public static PipelineResult runPipeline(ImportOptions options) throws IOException { /* * Steps: * 1. Read messages from Feast Source as FeatureRow @@ -80,8 +85,10 @@ public static PipelineResult runPipeline(ImportOptions options) log.info("Starting import job with settings: \n{}", options.toString()); - List featureSets = - SpecUtil.parseFeatureSetSpecJsonList(options.getFeatureSetJson()); + BZip2Decompressor> decompressor = + new BZip2Decompressor<>(new StringListStreamConverter()); + List featureSetJson = decompressor.decompress(options.getFeatureSetJson()); + List featureSets = SpecUtil.parseFeatureSetSpecJsonList(featureSetJson); List stores = SpecUtil.parseStoreJsonList(options.getStoreJson()); for (Store store : stores) { @@ -89,17 +96,24 @@ public static PipelineResult runPipeline(ImportOptions options) SpecUtil.getSubscribedFeatureSets(store.getSubscriptionsList(), featureSets); // Generate tags by key - Map featureSetsByKey = new HashMap<>(); + Map featureSetSpecsByKey = new HashMap<>(); subscribedFeatureSets.stream() .forEach( fs -> { - String ref = getFeatureSetReference(fs); - featureSetsByKey.put(ref, fs); + String ref = getFeatureSetReference(fs.getSpec()); + featureSetSpecsByKey.put(ref, fs.getSpec()); }); + FeatureSink featureSink = getFeatureSink(store, featureSetSpecsByKey); + // TODO: make the source part of the job initialisation options Source source = subscribedFeatureSets.get(0).getSpec().getSource(); + for (FeatureSet featureSet : subscribedFeatureSets) { + // Ensure Store has valid configuration and Feast can access it. + featureSink.prepareWrite(featureSet); + } + // Step 1. Read messages from Feast Source as FeatureRow. PCollectionTuple convertedFeatureRows = pipeline.apply( @@ -110,58 +124,49 @@ public static PipelineResult runPipeline(ImportOptions options) .setFailureTag(DEADLETTER_OUT) .build()); - for (FeatureSet featureSet : subscribedFeatureSets) { - // Ensure Store has valid configuration and Feast can access it. - StoreUtil.setupStore(store, featureSet); - } - - // Step 2. Validate incoming FeatureRows + // Step 2. Process and validate incoming FeatureRows PCollectionTuple validatedRows = convertedFeatureRows .get(FEATURE_ROW_OUT) .apply( - ValidateFeatureRows.newBuilder() - .setFeatureSets(featureSetsByKey) + ProcessAndValidateFeatureRows.newBuilder() + .setDefaultProject(options.getDefaultFeastProject()) + .setFeatureSetSpecs(featureSetSpecsByKey) .setSuccessTag(FEATURE_ROW_OUT) .setFailureTag(DEADLETTER_OUT) .build()); // Step 3. Write FeatureRow to the corresponding Store. - validatedRows - .get(FEATURE_ROW_OUT) - .apply( - "WriteFeatureRowToStore", - WriteToStore.newBuilder().setFeatureSets(featureSetsByKey).setStore(store).build()); + WriteResult writeFeatureRows = + validatedRows.get(FEATURE_ROW_OUT).apply("WriteFeatureRowToStore", featureSink.writer()); // Step 4. Write FailedElements to a dead letter table in BigQuery. if (options.getDeadLetterTableSpec() != null) { + // TODO: make deadletter destination type configurable + DeadletterSink deadletterSink = + new BigQueryDeadletterSink(options.getDeadLetterTableSpec()); + convertedFeatureRows .get(DEADLETTER_OUT) - .apply( - "WriteFailedElements_ReadFromSource", - WriteFailedElementToBigQuery.newBuilder() - .setJsonSchema(ResourceUtil.getDeadletterTableSchemaJson()) - .setTableSpec(options.getDeadLetterTableSpec()) - .build()); + .apply("WriteFailedElements_ReadFromSource", deadletterSink.write()); validatedRows .get(DEADLETTER_OUT) - .apply( - "WriteFailedElements_ValidateRows", - WriteFailedElementToBigQuery.newBuilder() - .setJsonSchema(ResourceUtil.getDeadletterTableSchemaJson()) - .setTableSpec(options.getDeadLetterTableSpec()) - .build()); + .apply("WriteFailedElements_ValidateRows", deadletterSink.write()); + + writeFeatureRows + .getFailedInserts() + .apply("WriteFailedElements_WriteFeatureRowToStore", deadletterSink.write()); } // Step 5. Write metrics to a metrics sink. - validatedRows.apply( - "WriteMetrics", - WriteMetricsTransform.newBuilder() - .setStoreName(store.getName()) - .setSuccessTag(FEATURE_ROW_OUT) - .setFailureTag(DEADLETTER_OUT) - .build()); + writeFeatureRows + .getSuccessfulInserts() + .apply("WriteSuccessMetrics", WriteSuccessMetricsTransform.create(store.getName())); + + writeFeatureRows + .getFailedInserts() + .apply("WriteFailureMetrics", WriteFailureMetricsTransform.create(store.getName())); } return pipeline.run(); diff --git a/ingestion/src/main/java/feast/ingestion/options/BZip2Compressor.java b/ingestion/src/main/java/feast/ingestion/options/BZip2Compressor.java new file mode 100644 index 00000000000..b7e4e6ee0af --- /dev/null +++ b/ingestion/src/main/java/feast/ingestion/options/BZip2Compressor.java @@ -0,0 +1,47 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.ingestion.options; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream; + +public class BZip2Compressor implements OptionCompressor { + + private final OptionByteConverter byteConverter; + + public BZip2Compressor(OptionByteConverter byteConverter) { + this.byteConverter = byteConverter; + } + /** + * Compress pipeline option using BZip2 + * + * @param option Pipeline option value + * @return BZip2 compressed option value + * @throws IOException + */ + @Override + public byte[] compress(T option) throws IOException { + ByteArrayOutputStream compressedStream = new ByteArrayOutputStream(); + try (BZip2CompressorOutputStream bzip2Output = + new BZip2CompressorOutputStream(compressedStream)) { + bzip2Output.write(byteConverter.toByte(option)); + } + + return compressedStream.toByteArray(); + } +} diff --git a/ingestion/src/main/java/feast/ingestion/options/BZip2Decompressor.java b/ingestion/src/main/java/feast/ingestion/options/BZip2Decompressor.java new file mode 100644 index 00000000000..ce49c1be6e6 --- /dev/null +++ b/ingestion/src/main/java/feast/ingestion/options/BZip2Decompressor.java @@ -0,0 +1,38 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.ingestion.options; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; + +public class BZip2Decompressor implements OptionDecompressor { + + private final InputStreamConverter inputStreamConverter; + + public BZip2Decompressor(InputStreamConverter inputStreamConverter) { + this.inputStreamConverter = inputStreamConverter; + } + + @Override + public T decompress(byte[] compressed) throws IOException { + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(compressed); + BZip2CompressorInputStream bzip2Input = new BZip2CompressorInputStream(inputStream)) { + return inputStreamConverter.readStream(bzip2Input); + } + } +} diff --git a/ingestion/src/main/java/feast/ingestion/options/ImportOptions.java b/ingestion/src/main/java/feast/ingestion/options/ImportOptions.java index b299bb47e55..e3a1b841c4a 100644 --- a/ingestion/src/main/java/feast/ingestion/options/ImportOptions.java +++ b/ingestion/src/main/java/feast/ingestion/options/ImportOptions.java @@ -26,18 +26,26 @@ /** Options passed to Beam to influence the job's execution environment */ public interface ImportOptions extends PipelineOptions, DataflowPipelineOptions, DirectOptions { + + @Required + @Description( + "Default feast project to apply to incoming rows that do not specify project in its feature set reference.") + String getDefaultFeastProject(); + + void setDefaultFeastProject(String defaultProject); + @Required @Description( - "JSON string representation of the FeatureSet that the import job will process." + "JSON string representation of the FeatureSet that the import job will process, in BZip2 binary format." + "FeatureSet follows the format in feast.core.FeatureSet proto." + "Mutliple FeatureSetSpec can be passed by specifying '--featureSet={...}' multiple times" + "The conversion of Proto message to JSON should follow this mapping:" + "https://developers.google.com/protocol-buffers/docs/proto3#json" + "Please minify and remove all insignificant whitespace such as newline in the JSON string" + "to prevent error when parsing the options") - List getFeatureSetJson(); + byte[] getFeatureSetJson(); - void setFeatureSetJson(List featureSetJson); + void setFeatureSetJson(byte[] featureSetJson); @Required @Description( @@ -64,8 +72,7 @@ public interface ImportOptions extends PipelineOptions, DataflowPipelineOptions, */ void setDeadLetterTableSpec(String deadLetterTableSpec); - // TODO: expound - @Description("MetricsAccumulator exporter type to instantiate.") + @Description("MetricsAccumulator exporter type to instantiate. Supported type: statsd") @Default.String("none") String getMetricsExporterType(); @@ -83,4 +90,14 @@ public interface ImportOptions extends PipelineOptions, DataflowPipelineOptions, int getStatsdPort(); void setStatsdPort(int StatsdPort); + + @Description( + "Fixed window size in seconds (default 60) to apply before aggregating the numerical value of " + + "features and exporting the aggregated values as metrics. Refer to " + + "feast/ingestion/transform/metrics/WriteFeatureValueMetricsDoFn.java" + + "for the metric nameas and types used.") + @Default.Integer(60) + int getWindowSizeInSecForFeatureValueMetric(); + + void setWindowSizeInSecForFeatureValueMetric(int seconds); } diff --git a/ingestion/src/main/java/feast/ingestion/options/InputStreamConverter.java b/ingestion/src/main/java/feast/ingestion/options/InputStreamConverter.java new file mode 100644 index 00000000000..e2fef732368 --- /dev/null +++ b/ingestion/src/main/java/feast/ingestion/options/InputStreamConverter.java @@ -0,0 +1,31 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.ingestion.options; + +import java.io.IOException; +import java.io.InputStream; + +public interface InputStreamConverter { + + /** + * Used in conjunction with {@link OptionDecompressor} to decompress the pipeline option + * + * @param inputStream Input byte stream in compressed format + * @return Decompressed pipeline option value + */ + T readStream(InputStream inputStream) throws IOException; +} diff --git a/ingestion/src/main/proto/feast_ingestion/types/CoalesceKey.proto b/ingestion/src/main/java/feast/ingestion/options/OptionByteConverter.java similarity index 55% rename from ingestion/src/main/proto/feast_ingestion/types/CoalesceKey.proto rename to ingestion/src/main/java/feast/ingestion/options/OptionByteConverter.java index 9730b49ec3b..ff5a41a627d 100644 --- a/ingestion/src/main/proto/feast_ingestion/types/CoalesceKey.proto +++ b/ingestion/src/main/java/feast/ingestion/options/OptionByteConverter.java @@ -1,5 +1,6 @@ /* - * Copyright 2018 The Feast Authors + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,13 +14,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package feast.ingestion.options; -syntax = "proto3"; +import java.io.IOException; -option java_package = "feast_ingestion.types"; -option java_outer_classname = "CoalesceKeyProto"; +public interface OptionByteConverter { -message CoalesceKey { - string entityName = 1; - string entityKey = 2; -} \ No newline at end of file + /** + * Used in conjunction with {@link OptionCompressor} to compress the pipeline option + * + * @param option Pipeline option value + * @return byte representation of the pipeline option value, without compression. + */ + byte[] toByte(T option) throws IOException; +} diff --git a/ingestion/src/main/java/feast/ingestion/options/OptionCompressor.java b/ingestion/src/main/java/feast/ingestion/options/OptionCompressor.java new file mode 100644 index 00000000000..b2345fc3eb1 --- /dev/null +++ b/ingestion/src/main/java/feast/ingestion/options/OptionCompressor.java @@ -0,0 +1,31 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.ingestion.options; + +import java.io.IOException; + +public interface OptionCompressor { + + /** + * Compress pipeline option into bytes format. This is necessary as some Beam runner has + * limitation in terms of pipeline option size. + * + * @param option Pipeline option value + * @return Compressed values of the option, as byte array + */ + byte[] compress(T option) throws IOException; +} diff --git a/core/src/main/java/feast/core/dao/MetricsRepository.java b/ingestion/src/main/java/feast/ingestion/options/OptionDecompressor.java similarity index 62% rename from core/src/main/java/feast/core/dao/MetricsRepository.java rename to ingestion/src/main/java/feast/ingestion/options/OptionDecompressor.java index 7146e1e3ecb..affeafdaa0b 100644 --- a/core/src/main/java/feast/core/dao/MetricsRepository.java +++ b/ingestion/src/main/java/feast/ingestion/options/OptionDecompressor.java @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: Apache-2.0 - * Copyright 2018-2019 The Feast Authors + * Copyright 2018-2020 The Feast Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,14 +14,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package feast.core.dao; +package feast.ingestion.options; -import feast.core.model.Metrics; -import java.util.List; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; +import java.io.IOException; -@Repository -public interface MetricsRepository extends JpaRepository { - List findByJob_Id(String id); +public interface OptionDecompressor { + + /** + * Decompress pipeline option from byte array. + * + * @param compressed Compressed pipeline option value + * @return Decompressed pipeline option + */ + T decompress(byte[] compressed) throws IOException; } diff --git a/ingestion/src/main/java/feast/ingestion/options/StringListStreamConverter.java b/ingestion/src/main/java/feast/ingestion/options/StringListStreamConverter.java new file mode 100644 index 00000000000..d7277f3c7d6 --- /dev/null +++ b/ingestion/src/main/java/feast/ingestion/options/StringListStreamConverter.java @@ -0,0 +1,41 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.ingestion.options; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.List; +import java.util.stream.Collectors; + +public class StringListStreamConverter implements InputStreamConverter> { + + /** + * Convert Input byte stream to newline separated strings + * + * @param inputStream Input byte stream + * @return List of string + */ + @Override + public List readStream(InputStream inputStream) throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + List stringList = reader.lines().collect(Collectors.toList()); + reader.close(); + return stringList; + } +} diff --git a/ingestion/src/main/java/feast/ingestion/transform/ValidateFeatureRows.java b/ingestion/src/main/java/feast/ingestion/transform/ProcessAndValidateFeatureRows.java similarity index 60% rename from ingestion/src/main/java/feast/ingestion/transform/ValidateFeatureRows.java rename to ingestion/src/main/java/feast/ingestion/transform/ProcessAndValidateFeatureRows.java index 5ca6a710f62..2ce8918ccf9 100644 --- a/ingestion/src/main/java/feast/ingestion/transform/ValidateFeatureRows.java +++ b/ingestion/src/main/java/feast/ingestion/transform/ProcessAndValidateFeatureRows.java @@ -17,11 +17,12 @@ package feast.ingestion.transform; import com.google.auto.value.AutoValue; -import feast.core.FeatureSetProto; +import feast.ingestion.transform.fn.ProcessFeatureRowDoFn; import feast.ingestion.transform.fn.ValidateFeatureRowDoFn; -import feast.ingestion.values.FailedElement; import feast.ingestion.values.FeatureSet; -import feast.types.FeatureRowProto.FeatureRow; +import feast.proto.core.FeatureSetProto; +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.storage.api.writer.FailedElement; import java.util.Map; import java.util.stream.Collectors; import org.apache.beam.sdk.transforms.PTransform; @@ -33,47 +34,54 @@ import org.apache.commons.lang3.tuple.Pair; @AutoValue -public abstract class ValidateFeatureRows +public abstract class ProcessAndValidateFeatureRows extends PTransform, PCollectionTuple> { - public abstract Map getFeatureSets(); + public abstract Map getFeatureSetSpecs(); + + public abstract String getDefaultProject(); public abstract TupleTag getSuccessTag(); public abstract TupleTag getFailureTag(); public static Builder newBuilder() { - return new AutoValue_ValidateFeatureRows.Builder(); + return new AutoValue_ProcessAndValidateFeatureRows.Builder(); } @AutoValue.Builder public abstract static class Builder { - public abstract Builder setFeatureSets(Map featureSets); + public abstract Builder setFeatureSetSpecs( + Map featureSets); + + public abstract Builder setDefaultProject(String defaultProject); public abstract Builder setSuccessTag(TupleTag successTag); public abstract Builder setFailureTag(TupleTag failureTag); - public abstract ValidateFeatureRows build(); + public abstract ProcessAndValidateFeatureRows build(); } @Override public PCollectionTuple expand(PCollection input) { Map featureSets = - getFeatureSets().entrySet().stream() + getFeatureSetSpecs().entrySet().stream() .map(e -> Pair.of(e.getKey(), new FeatureSet(e.getValue()))) .collect(Collectors.toMap(Pair::getLeft, Pair::getRight)); - return input.apply( - "ValidateFeatureRows", - ParDo.of( - ValidateFeatureRowDoFn.newBuilder() - .setFeatureSets(featureSets) - .setSuccessTag(getSuccessTag()) - .setFailureTag(getFailureTag()) - .build()) - .withOutputTags(getSuccessTag(), TupleTagList.of(getFailureTag()))); + return input + .apply("ProcessFeatureRows", ParDo.of(new ProcessFeatureRowDoFn(getDefaultProject()))) + .apply( + "ValidateFeatureRows", + ParDo.of( + ValidateFeatureRowDoFn.newBuilder() + .setFeatureSets(featureSets) + .setSuccessTag(getSuccessTag()) + .setFailureTag(getFailureTag()) + .build()) + .withOutputTags(getSuccessTag(), TupleTagList.of(getFailureTag()))); } } diff --git a/ingestion/src/main/java/feast/ingestion/transform/ReadFromSource.java b/ingestion/src/main/java/feast/ingestion/transform/ReadFromSource.java index 65e95b287dc..45253735141 100644 --- a/ingestion/src/main/java/feast/ingestion/transform/ReadFromSource.java +++ b/ingestion/src/main/java/feast/ingestion/transform/ReadFromSource.java @@ -18,11 +18,11 @@ import com.google.auto.value.AutoValue; import com.google.common.base.Preconditions; -import feast.core.SourceProto.Source; -import feast.core.SourceProto.SourceType; import feast.ingestion.transform.fn.KafkaRecordToFeatureRowDoFn; -import feast.ingestion.values.FailedElement; -import feast.types.FeatureRowProto.FeatureRow; +import feast.proto.core.SourceProto.Source; +import feast.proto.core.SourceProto.SourceType; +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.storage.api.writer.FailedElement; import org.apache.beam.sdk.io.kafka.KafkaIO; import org.apache.beam.sdk.transforms.PTransform; import org.apache.beam.sdk.transforms.ParDo; @@ -61,7 +61,7 @@ public ReadFromSource build() { Source source = read.getSource(); Preconditions.checkState( source.getType().equals(SourceType.KAFKA), - "Source type must be KAFKA. Please raise an issue in https://github.com/gojek/feast/issues to request additional source types."); + "Source type must be KAFKA. Please raise an issue in https://github.com/feast-dev/feast/issues to request additional source types."); Preconditions.checkState( !source.getKafkaSourceConfig().getBootstrapServers().isEmpty(), "bootstrap_servers cannot be empty."); diff --git a/ingestion/src/main/java/feast/ingestion/transform/WriteFailedElementToBigQuery.java b/ingestion/src/main/java/feast/ingestion/transform/WriteFailedElementToBigQuery.java index cda590b21aa..0da281790c5 100644 --- a/ingestion/src/main/java/feast/ingestion/transform/WriteFailedElementToBigQuery.java +++ b/ingestion/src/main/java/feast/ingestion/transform/WriteFailedElementToBigQuery.java @@ -18,7 +18,7 @@ import com.google.api.services.bigquery.model.TableRow; import com.google.auto.value.AutoValue; -import feast.ingestion.values.FailedElement; +import feast.storage.api.writer.FailedElement; import org.apache.beam.sdk.io.gcp.bigquery.BigQueryIO; import org.apache.beam.sdk.io.gcp.bigquery.BigQueryIO.Write.CreateDisposition; import org.apache.beam.sdk.io.gcp.bigquery.BigQueryIO.Write.WriteDisposition; diff --git a/ingestion/src/main/java/feast/ingestion/transform/WriteToStore.java b/ingestion/src/main/java/feast/ingestion/transform/WriteToStore.java deleted file mode 100644 index 778540595a2..00000000000 --- a/ingestion/src/main/java/feast/ingestion/transform/WriteToStore.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright 2018-2019 The Feast Authors - * - * 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 feast.ingestion.transform; - -import com.google.api.services.bigquery.model.TableDataInsertAllResponse.InsertErrors; -import com.google.api.services.bigquery.model.TableRow; -import com.google.auto.value.AutoValue; -import feast.core.FeatureSetProto.FeatureSet; -import feast.core.StoreProto.Store; -import feast.core.StoreProto.Store.BigQueryConfig; -import feast.core.StoreProto.Store.RedisConfig; -import feast.core.StoreProto.Store.StoreType; -import feast.ingestion.options.ImportOptions; -import feast.ingestion.utils.ResourceUtil; -import feast.ingestion.values.FailedElement; -import feast.store.serving.bigquery.FeatureRowToTableRow; -import feast.store.serving.bigquery.GetTableDestination; -import feast.store.serving.redis.FeatureRowToRedisMutationDoFn; -import feast.store.serving.redis.RedisCustomIO; -import feast.types.FeatureRowProto.FeatureRow; -import java.io.IOException; -import java.util.Map; -import org.apache.beam.sdk.io.gcp.bigquery.BigQueryIO; -import org.apache.beam.sdk.io.gcp.bigquery.BigQueryIO.Write.CreateDisposition; -import org.apache.beam.sdk.io.gcp.bigquery.BigQueryIO.Write.Method; -import org.apache.beam.sdk.io.gcp.bigquery.BigQueryIO.Write.WriteDisposition; -import org.apache.beam.sdk.io.gcp.bigquery.BigQueryInsertError; -import org.apache.beam.sdk.io.gcp.bigquery.InsertRetryPolicy; -import org.apache.beam.sdk.io.gcp.bigquery.WriteResult; -import org.apache.beam.sdk.metrics.Counter; -import org.apache.beam.sdk.metrics.Metrics; -import org.apache.beam.sdk.transforms.DoFn; -import org.apache.beam.sdk.transforms.MapElements; -import org.apache.beam.sdk.transforms.PTransform; -import org.apache.beam.sdk.transforms.ParDo; -import org.apache.beam.sdk.values.PCollection; -import org.apache.beam.sdk.values.PDone; -import org.apache.beam.sdk.values.TypeDescriptors; -import org.slf4j.Logger; - -@AutoValue -public abstract class WriteToStore extends PTransform, PDone> { - - private static final Logger log = org.slf4j.LoggerFactory.getLogger(WriteToStore.class); - - public static final String METRIC_NAMESPACE = "WriteToStore"; - public static final String ELEMENTS_WRITTEN_METRIC = "elements_written"; - - private static final Counter elementsWritten = - Metrics.counter(METRIC_NAMESPACE, ELEMENTS_WRITTEN_METRIC); - - public abstract Store getStore(); - - public abstract Map getFeatureSets(); - - public static Builder newBuilder() { - return new AutoValue_WriteToStore.Builder(); - } - - @AutoValue.Builder - public abstract static class Builder { - - public abstract Builder setStore(Store store); - - public abstract Builder setFeatureSets(Map featureSets); - - public abstract WriteToStore build(); - } - - @Override - public PDone expand(PCollection input) { - ImportOptions options = input.getPipeline().getOptions().as(ImportOptions.class); - StoreType storeType = getStore().getType(); - - switch (storeType) { - case REDIS: - RedisConfig redisConfig = getStore().getRedisConfig(); - PCollection redisWriteResult = input - .apply( - "FeatureRowToRedisMutation", - ParDo.of(new FeatureRowToRedisMutationDoFn(getFeatureSets()))) - .apply( - "WriteRedisMutationToRedis", - RedisCustomIO.write(redisConfig)); - if (options.getDeadLetterTableSpec() != null) { - redisWriteResult.apply( - WriteFailedElementToBigQuery.newBuilder() - .setTableSpec(options.getDeadLetterTableSpec()) - .setJsonSchema(ResourceUtil.getDeadletterTableSchemaJson()) - .build()); - } - break; - case BIGQUERY: - BigQueryConfig bigqueryConfig = getStore().getBigqueryConfig(); - - WriteResult bigqueryWriteResult = - input.apply( - "WriteTableRowToBigQuery", - BigQueryIO.write() - .to( - new GetTableDestination( - bigqueryConfig.getProjectId(), bigqueryConfig.getDatasetId())) - .withFormatFunction(new FeatureRowToTableRow(options.getJobName())) - .withCreateDisposition(CreateDisposition.CREATE_NEVER) - .withWriteDisposition(WriteDisposition.WRITE_APPEND) - .withExtendedErrorInfo() - .withMethod(Method.STREAMING_INSERTS) - .withFailedInsertRetryPolicy(InsertRetryPolicy.retryTransientErrors())); - - if (options.getDeadLetterTableSpec() != null) { - bigqueryWriteResult - .getFailedInsertsWithErr() - .apply( - "WrapBigQueryInsertionError", - ParDo.of( - new DoFn() { - @ProcessElement - public void processElement(ProcessContext context) { - InsertErrors error = context.element().getError(); - TableRow row = context.element().getRow(); - try { - context.output( - FailedElement.newBuilder() - .setErrorMessage(error.toPrettyString()) - .setPayload(row.toPrettyString()) - .setJobName(context.getPipelineOptions().getJobName()) - .setTransformName("WriteTableRowToBigQuery") - .build()); - } catch (IOException e) { - log.error(e.getMessage()); - } - } - })) - .apply( - WriteFailedElementToBigQuery.newBuilder() - .setTableSpec(options.getDeadLetterTableSpec()) - .setJsonSchema(ResourceUtil.getDeadletterTableSchemaJson()) - .build()); - } - break; - default: - log.error("Store type '{}' is not supported. No Feature Row will be written.", storeType); - break; - } - - input.apply( - "IncrementWriteToStoreElementsWrittenCounter", - MapElements.into(TypeDescriptors.booleans()) - .via( - (FeatureRow row) -> { - elementsWritten.inc(); - return true; - })); - - return PDone.in(input.getPipeline()); - } -} diff --git a/ingestion/src/main/java/feast/ingestion/transform/fn/KafkaRecordToFeatureRowDoFn.java b/ingestion/src/main/java/feast/ingestion/transform/fn/KafkaRecordToFeatureRowDoFn.java index 25aafd6ee71..7654c1e2fa0 100644 --- a/ingestion/src/main/java/feast/ingestion/transform/fn/KafkaRecordToFeatureRowDoFn.java +++ b/ingestion/src/main/java/feast/ingestion/transform/fn/KafkaRecordToFeatureRowDoFn.java @@ -18,9 +18,8 @@ import com.google.auto.value.AutoValue; import com.google.protobuf.InvalidProtocolBufferException; -import feast.ingestion.transform.ReadFromSource.Builder; -import feast.ingestion.values.FailedElement; -import feast.types.FeatureRowProto.FeatureRow; +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.storage.api.writer.FailedElement; import java.util.Base64; import org.apache.beam.sdk.io.kafka.KafkaRecord; import org.apache.beam.sdk.transforms.DoFn; diff --git a/ingestion/src/main/java/feast/ingestion/transform/fn/ProcessFeatureRowDoFn.java b/ingestion/src/main/java/feast/ingestion/transform/fn/ProcessFeatureRowDoFn.java new file mode 100644 index 00000000000..3680348cf0c --- /dev/null +++ b/ingestion/src/main/java/feast/ingestion/transform/fn/ProcessFeatureRowDoFn.java @@ -0,0 +1,52 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.ingestion.transform.fn; + +import feast.proto.types.FeatureRowProto.FeatureRow; +import org.apache.beam.sdk.transforms.DoFn; + +public class ProcessFeatureRowDoFn extends DoFn { + + private String defaultProject; + + public ProcessFeatureRowDoFn(String defaultProject) { + this.defaultProject = defaultProject; + } + + @ProcessElement + public void processElement(ProcessContext context) { + FeatureRow featureRow = context.element(); + String featureSetId = stripVersion(featureRow.getFeatureSet()); + featureSetId = applyDefaultProject(featureSetId); + featureRow = featureRow.toBuilder().setFeatureSet(featureSetId).build(); + context.output(featureRow); + } + + // For backward compatibility. Will be deprecated eventually. + private String stripVersion(String featureSetId) { + String[] split = featureSetId.split(":"); + return split[0]; + } + + private String applyDefaultProject(String featureSetId) { + String[] split = featureSetId.split("/"); + if (split.length == 1) { + return defaultProject + "/" + featureSetId; + } + return featureSetId; + } +} diff --git a/ingestion/src/main/java/feast/ingestion/transform/fn/ValidateFeatureRowDoFn.java b/ingestion/src/main/java/feast/ingestion/transform/fn/ValidateFeatureRowDoFn.java index 7d61a62f3fc..856d94828ad 100644 --- a/ingestion/src/main/java/feast/ingestion/transform/fn/ValidateFeatureRowDoFn.java +++ b/ingestion/src/main/java/feast/ingestion/transform/fn/ValidateFeatureRowDoFn.java @@ -17,17 +17,15 @@ package feast.ingestion.transform.fn; import com.google.auto.value.AutoValue; -import feast.ingestion.values.FailedElement; import feast.ingestion.values.FeatureSet; import feast.ingestion.values.Field; -import feast.types.FeatureRowProto.FeatureRow; -import feast.types.FieldProto; -import feast.types.ValueProto.Value.ValCase; +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.proto.types.FieldProto; +import feast.proto.types.ValueProto.Value.ValCase; +import feast.storage.api.writer.FailedElement; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import org.apache.beam.sdk.transforms.DoFn; import org.apache.beam.sdk.values.TupleTag; @@ -60,14 +58,14 @@ public abstract static class Builder { public void processElement(ProcessContext context) { String error = null; FeatureRow featureRow = context.element(); - FeatureSet featureSet = getFeatureSets().getOrDefault(featureRow.getFeatureSet(), null); + FeatureSet featureSet = getFeatureSets().get(featureRow.getFeatureSet()); List fields = new ArrayList<>(); if (featureSet != null) { for (FieldProto.Field field : featureRow.getFieldsList()) { Field fieldSpec = featureSet.getField(field.getName()); if (fieldSpec == null) { // skip - break; + continue; } // If value is set in the FeatureRow, make sure the value type matches // that defined in FeatureSetSpec @@ -101,20 +99,12 @@ public void processElement(ProcessContext context) { .setPayload(featureRow.toString()) .setErrorMessage(error); if (featureSet != null) { - String[] split = featureSet.getReference().split(":"); - String[] nameSplit = split[0].split("/"); - failedElement = - failedElement - .setProjectName(nameSplit[0]) - .setFeatureSetName(nameSplit[1]) - .setFeatureSetVersion(split[1]); + String[] split = featureSet.getReference().split("/"); + failedElement = failedElement.setProjectName(split[0]).setFeatureSetName(split[1]); } context.output(getFailureTag(), failedElement.build()); } else { - featureRow = featureRow.toBuilder() - .clearFields() - .addAllFields(fields) - .build(); + featureRow = featureRow.toBuilder().clearFields().addAllFields(fields).build(); context.output(getSuccessTag(), featureRow); } } diff --git a/ingestion/src/main/java/feast/ingestion/transform/metrics/WriteDeadletterRowMetricsDoFn.java b/ingestion/src/main/java/feast/ingestion/transform/metrics/WriteDeadletterRowMetricsDoFn.java index 687670c5cf0..828fed6ceda 100644 --- a/ingestion/src/main/java/feast/ingestion/transform/metrics/WriteDeadletterRowMetricsDoFn.java +++ b/ingestion/src/main/java/feast/ingestion/transform/metrics/WriteDeadletterRowMetricsDoFn.java @@ -20,7 +20,7 @@ import com.timgroup.statsd.NonBlockingStatsDClient; import com.timgroup.statsd.StatsDClient; import com.timgroup.statsd.StatsDClientException; -import feast.ingestion.values.FailedElement; +import feast.storage.api.writer.FailedElement; import org.apache.beam.sdk.transforms.DoFn; import org.slf4j.Logger; @@ -76,7 +76,6 @@ public void processElement(ProcessContext c) { STORE_TAG_KEY + ":" + getStoreName(), PROJECT_TAG_KEY + ":" + ignored.getProjectName(), FEATURE_SET_NAME_TAG_KEY + ":" + ignored.getFeatureSetName(), - FEATURE_SET_VERSION_TAG_KEY + ":" + ignored.getFeatureSetVersion(), INGESTION_JOB_NAME_KEY + ":" + c.getPipelineOptions().getJobName()); } catch (StatsDClientException e) { log.warn("Unable to push metrics to server", e); diff --git a/ingestion/src/main/java/feast/ingestion/transform/metrics/WriteFailureMetricsTransform.java b/ingestion/src/main/java/feast/ingestion/transform/metrics/WriteFailureMetricsTransform.java new file mode 100644 index 00000000000..778515ae8a8 --- /dev/null +++ b/ingestion/src/main/java/feast/ingestion/transform/metrics/WriteFailureMetricsTransform.java @@ -0,0 +1,61 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2019 The Feast Authors + * + * 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 feast.ingestion.transform.metrics; + +import com.google.auto.value.AutoValue; +import feast.ingestion.options.ImportOptions; +import feast.storage.api.writer.FailedElement; +import org.apache.beam.sdk.transforms.DoFn; +import org.apache.beam.sdk.transforms.PTransform; +import org.apache.beam.sdk.transforms.ParDo; +import org.apache.beam.sdk.values.PCollection; +import org.apache.beam.sdk.values.PDone; + +@AutoValue +public abstract class WriteFailureMetricsTransform + extends PTransform, PDone> { + + public abstract String getStoreName(); + + public static WriteFailureMetricsTransform create(String storeName) { + return new AutoValue_WriteFailureMetricsTransform(storeName); + } + + @Override + public PDone expand(PCollection input) { + ImportOptions options = input.getPipeline().getOptions().as(ImportOptions.class); + if ("statsd".equals(options.getMetricsExporterType())) { + input.apply( + "WriteDeadletterMetrics", + ParDo.of( + WriteDeadletterRowMetricsDoFn.newBuilder() + .setStatsdHost(options.getStatsdHost()) + .setStatsdPort(options.getStatsdPort()) + .setStoreName(getStoreName()) + .build())); + } else { + input.apply( + "Noop", + ParDo.of( + new DoFn() { + @ProcessElement + public void processElement(ProcessContext c) {} + })); + } + return PDone.in(input.getPipeline()); + } +} diff --git a/ingestion/src/main/java/feast/ingestion/transform/metrics/WriteFeatureValueMetricsDoFn.java b/ingestion/src/main/java/feast/ingestion/transform/metrics/WriteFeatureValueMetricsDoFn.java new file mode 100644 index 00000000000..809687a6b2c --- /dev/null +++ b/ingestion/src/main/java/feast/ingestion/transform/metrics/WriteFeatureValueMetricsDoFn.java @@ -0,0 +1,318 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.ingestion.transform.metrics; + +import static feast.ingestion.transform.metrics.WriteRowMetricsDoFn.FEATURE_SET_NAME_TAG_KEY; +import static feast.ingestion.transform.metrics.WriteRowMetricsDoFn.FEATURE_SET_PROJECT_TAG_KEY; +import static feast.ingestion.transform.metrics.WriteRowMetricsDoFn.FEATURE_TAG_KEY; +import static feast.ingestion.transform.metrics.WriteRowMetricsDoFn.INGESTION_JOB_NAME_KEY; +import static feast.ingestion.transform.metrics.WriteRowMetricsDoFn.METRIC_PREFIX; +import static feast.ingestion.transform.metrics.WriteRowMetricsDoFn.STORE_TAG_KEY; + +import com.google.auto.value.AutoValue; +import com.timgroup.statsd.NonBlockingStatsDClient; +import com.timgroup.statsd.StatsDClient; +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.proto.types.FieldProto.Field; +import feast.proto.types.ValueProto.Value; +import java.util.ArrayList; +import java.util.DoubleSummaryStatistics; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import org.apache.beam.sdk.transforms.DoFn; +import org.apache.beam.sdk.values.KV; +import org.apache.commons.math3.stat.descriptive.rank.Percentile; +import org.slf4j.Logger; + +/** + * WriteFeatureValueMetricsDoFn accepts key value of FeatureSetRef(str) to FeatureRow(List) and + * writes a histogram of the numerical values of each feature to StatsD. + * + *

The histogram of the numerical values is represented as the following in StatsD: + * + *

    + *
  • gauge of feature_value_min + *
  • gauge of feature_value_max + *
  • gauge of feature_value_mean + *
  • gauge of feature_value_percentile_50 + *
  • gauge of feature_value_percentile_90 + *
  • gauge of feature_value_percentile_95 + *
+ * + *

StatsD timing/histogram metric type is not used since it does not support negative values. + */ +@AutoValue +public abstract class WriteFeatureValueMetricsDoFn + extends DoFn>, Void> { + + abstract String getStoreName(); + + abstract String getStatsdHost(); + + abstract int getStatsdPort(); + + static Builder newBuilder() { + return new AutoValue_WriteFeatureValueMetricsDoFn.Builder(); + } + + @AutoValue.Builder + abstract static class Builder { + + abstract Builder setStoreName(String storeName); + + abstract Builder setStatsdHost(String statsdHost); + + abstract Builder setStatsdPort(int statsdPort); + + abstract WriteFeatureValueMetricsDoFn build(); + } + + private static final Logger log = + org.slf4j.LoggerFactory.getLogger(WriteFeatureValueMetricsDoFn.class); + private StatsDClient statsDClient; + public static String GAUGE_NAME_FEATURE_VALUE_MIN = "feature_value_min"; + public static String GAUGE_NAME_FEATURE_VALUE_MAX = "feature_value_max"; + public static String GAUGE_NAME_FEATURE_VALUE_MEAN = "feature_value_mean"; + public static String GAUGE_NAME_FEATURE_VALUE_PERCENTILE_25 = "feature_value_percentile_25"; + public static String GAUGE_NAME_FEATURE_VALUE_PERCENTILE_50 = "feature_value_percentile_50"; + public static String GAUGE_NAME_FEATURE_VALUE_PERCENTILE_90 = "feature_value_percentile_90"; + public static String GAUGE_NAME_FEATURE_VALUE_PERCENTILE_95 = "feature_value_percentile_95"; + public static String GAUGE_NAME_FEATURE_VALUE_PERCENTILE_99 = "feature_value_percentile_99"; + + @Setup + public void setup() { + // Note that exception may be thrown during StatsD client instantiation but no exception + // will be thrown when sending metrics (mimicking the UDP protocol behaviour). + // https://jar-download.com/artifacts/com.datadoghq/java-dogstatsd-client/2.1.1/documentation + // https://github.com/DataDog/java-dogstatsd-client#unix-domain-socket-support + try { + statsDClient = new NonBlockingStatsDClient(METRIC_PREFIX, getStatsdHost(), getStatsdPort()); + } catch (Exception e) { + log.error("StatsD client cannot be started: " + e.getMessage()); + } + } + + @Teardown + public void tearDown() { + if (statsDClient != null) { + statsDClient.close(); + } + } + + @ProcessElement + public void processElement( + ProcessContext context, + @Element KV> featureSetRefToFeatureRows) { + if (statsDClient == null) { + log.error("StatsD client is null, likely because it encounters an error during setup"); + return; + } + + String featureSetRef = featureSetRefToFeatureRows.getKey(); + if (featureSetRef == null) { + log.error( + "Feature set reference in the feature row is null. Please check the input feature rows from previous steps"); + return; + } + + String[] slashSplits = featureSetRef.split("/"); + if (slashSplits.length != 2) { + log.error( + "Skip writing feature value metrics because the feature set reference '{}' does not" + + "follow the required format /", + featureSetRef); + return; + } + String projectName = slashSplits[0]; + String featureSetName = slashSplits[1]; + + Map featureNameToStats = new HashMap<>(); + Map> featureNameToValues = new HashMap<>(); + for (FeatureRow featureRow : featureSetRefToFeatureRows.getValue()) { + for (Field field : featureRow.getFieldsList()) { + updateStats(featureNameToStats, featureNameToValues, field); + } + } + + for (Entry entry : featureNameToStats.entrySet()) { + String featureName = entry.getKey(); + DoubleSummaryStatistics stats = entry.getValue(); + String[] tags = { + STORE_TAG_KEY + ":" + getStoreName(), + FEATURE_SET_PROJECT_TAG_KEY + ":" + projectName, + FEATURE_SET_NAME_TAG_KEY + ":" + featureSetName, + FEATURE_TAG_KEY + ":" + featureName, + INGESTION_JOB_NAME_KEY + ":" + context.getPipelineOptions().getJobName() + }; + + // stats can return non finite values when there is no element + // or there is an element that is not a number. Metric should only be sent for finite values. + if (Double.isFinite(stats.getMin())) { + if (stats.getMin() < 0) { + // StatsD gauge will asssign a delta instead of the actual value, if there is a sign in + // the value. E.g. if the value is negative, a delta will be assigned. For this reason, + // the gauge value is set to zero beforehand. + // https://github.com/statsd/statsd/blob/master/docs/metric_types.md#gauges + statsDClient.gauge(GAUGE_NAME_FEATURE_VALUE_MIN, 0, tags); + } + statsDClient.gauge(GAUGE_NAME_FEATURE_VALUE_MIN, stats.getMin(), tags); + } + if (Double.isFinite(stats.getMax())) { + if (stats.getMax() < 0) { + statsDClient.gauge(GAUGE_NAME_FEATURE_VALUE_MAX, 0, tags); + } + statsDClient.gauge(GAUGE_NAME_FEATURE_VALUE_MAX, stats.getMax(), tags); + } + if (Double.isFinite(stats.getAverage())) { + if (stats.getAverage() < 0) { + statsDClient.gauge(GAUGE_NAME_FEATURE_VALUE_MEAN, 0, tags); + } + statsDClient.gauge(GAUGE_NAME_FEATURE_VALUE_MEAN, stats.getAverage(), tags); + } + + // For percentile calculation, Percentile class from commons-math3 from Apache is used. + // Percentile requires double[], hence the conversion below. + if (!featureNameToValues.containsKey(featureName)) { + continue; + } + List valueList = featureNameToValues.get(featureName); + if (valueList == null || valueList.size() < 1) { + continue; + } + double[] values = new double[valueList.size()]; + for (int i = 0; i < values.length; i++) { + values[i] = valueList.get(i); + } + + double p25 = new Percentile().evaluate(values, 25); + if (p25 < 0) { + statsDClient.gauge(GAUGE_NAME_FEATURE_VALUE_PERCENTILE_25, 0, tags); + } + statsDClient.gauge(GAUGE_NAME_FEATURE_VALUE_PERCENTILE_25, p25, tags); + + double p50 = new Percentile().evaluate(values, 50); + if (p50 < 0) { + statsDClient.gauge(GAUGE_NAME_FEATURE_VALUE_PERCENTILE_50, 0, tags); + } + statsDClient.gauge(GAUGE_NAME_FEATURE_VALUE_PERCENTILE_50, p50, tags); + + double p90 = new Percentile().evaluate(values, 90); + if (p90 < 0) { + statsDClient.gauge(GAUGE_NAME_FEATURE_VALUE_PERCENTILE_90, 0, tags); + } + statsDClient.gauge(GAUGE_NAME_FEATURE_VALUE_PERCENTILE_90, p90, tags); + + double p95 = new Percentile().evaluate(values, 95); + if (p95 < 0) { + statsDClient.gauge(GAUGE_NAME_FEATURE_VALUE_PERCENTILE_95, 0, tags); + } + statsDClient.gauge(GAUGE_NAME_FEATURE_VALUE_PERCENTILE_95, p95, tags); + + double p99 = new Percentile().evaluate(values, 99); + if (p99 < 0) { + statsDClient.gauge(GAUGE_NAME_FEATURE_VALUE_PERCENTILE_99, 0, tags); + } + statsDClient.gauge(GAUGE_NAME_FEATURE_VALUE_PERCENTILE_99, p99, tags); + } + } + + // Update stats and values array for the feature represented by the field. + // If the field contains non-numerical or non-boolean value, the stats and values array + // won't get updated because we are only concerned with numerical value in metrics data. + // For boolean value, true and false are treated as numerical value of 1 of 0 respectively. + private void updateStats( + Map featureNameToStats, + Map> featureNameToValues, + Field field) { + if (featureNameToStats == null || featureNameToValues == null || field == null) { + return; + } + + String featureName = field.getName(); + if (!featureNameToStats.containsKey(featureName)) { + featureNameToStats.put(featureName, new DoubleSummaryStatistics()); + } + if (!featureNameToValues.containsKey(featureName)) { + featureNameToValues.put(featureName, new ArrayList<>()); + } + + Value value = field.getValue(); + DoubleSummaryStatistics stats = featureNameToStats.get(featureName); + List values = featureNameToValues.get(featureName); + + switch (value.getValCase()) { + case INT32_VAL: + stats.accept(value.getInt32Val()); + values.add(((double) value.getInt32Val())); + break; + case INT64_VAL: + stats.accept(value.getInt64Val()); + values.add((double) value.getInt64Val()); + break; + case DOUBLE_VAL: + stats.accept(value.getDoubleVal()); + values.add(value.getDoubleVal()); + break; + case FLOAT_VAL: + stats.accept(value.getFloatVal()); + values.add((double) value.getFloatVal()); + break; + case BOOL_VAL: + stats.accept(value.getBoolVal() ? 1 : 0); + values.add(value.getBoolVal() ? 1d : 0d); + break; + case INT32_LIST_VAL: + for (Integer val : value.getInt32ListVal().getValList()) { + stats.accept(val); + values.add(((double) val)); + } + break; + case INT64_LIST_VAL: + for (Long val : value.getInt64ListVal().getValList()) { + stats.accept(val); + values.add(((double) val)); + } + break; + case DOUBLE_LIST_VAL: + for (Double val : value.getDoubleListVal().getValList()) { + stats.accept(val); + values.add(val); + } + break; + case FLOAT_LIST_VAL: + for (Float val : value.getFloatListVal().getValList()) { + stats.accept(val); + values.add(((double) val)); + } + break; + case BOOL_LIST_VAL: + for (Boolean val : value.getBoolListVal().getValList()) { + stats.accept(val ? 1 : 0); + values.add(val ? 1d : 0d); + } + break; + case BYTES_VAL: + case BYTES_LIST_VAL: + case STRING_VAL: + case STRING_LIST_VAL: + case VAL_NOT_SET: + default: + } + } +} diff --git a/ingestion/src/main/java/feast/ingestion/transform/metrics/WriteMetricsTransform.java b/ingestion/src/main/java/feast/ingestion/transform/metrics/WriteMetricsTransform.java deleted file mode 100644 index 43f314aa861..00000000000 --- a/ingestion/src/main/java/feast/ingestion/transform/metrics/WriteMetricsTransform.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright 2018-2019 The Feast Authors - * - * 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 feast.ingestion.transform.metrics; - -import com.google.auto.value.AutoValue; -import feast.ingestion.options.ImportOptions; -import feast.ingestion.values.FailedElement; -import feast.types.FeatureRowProto.FeatureRow; -import org.apache.beam.sdk.transforms.DoFn; -import org.apache.beam.sdk.transforms.PTransform; -import org.apache.beam.sdk.transforms.ParDo; -import org.apache.beam.sdk.values.PCollectionTuple; -import org.apache.beam.sdk.values.PDone; -import org.apache.beam.sdk.values.TupleTag; - -@AutoValue -public abstract class WriteMetricsTransform extends PTransform { - - public abstract String getStoreName(); - - public abstract TupleTag getSuccessTag(); - - public abstract TupleTag getFailureTag(); - - public static Builder newBuilder() { - return new AutoValue_WriteMetricsTransform.Builder(); - } - - @AutoValue.Builder - public abstract static class Builder { - - public abstract Builder setStoreName(String storeName); - - public abstract Builder setSuccessTag(TupleTag successTag); - - public abstract Builder setFailureTag(TupleTag failureTag); - - public abstract WriteMetricsTransform build(); - } - - @Override - public PDone expand(PCollectionTuple input) { - ImportOptions options = input.getPipeline().getOptions().as(ImportOptions.class); - switch (options.getMetricsExporterType()) { - case "statsd": - input - .get(getFailureTag()) - .apply( - "WriteDeadletterMetrics", - ParDo.of( - WriteDeadletterRowMetricsDoFn.newBuilder() - .setStatsdHost(options.getStatsdHost()) - .setStatsdPort(options.getStatsdPort()) - .setStoreName(getStoreName()) - .build())); - - input - .get(getSuccessTag()) - .apply( - "WriteRowMetrics", - ParDo.of( - WriteRowMetricsDoFn.newBuilder() - .setStatsdHost(options.getStatsdHost()) - .setStatsdPort(options.getStatsdPort()) - .setStoreName(getStoreName()) - .build())); - - return PDone.in(input.getPipeline()); - case "none": - default: - input - .get(getSuccessTag()) - .apply( - "Noop", - ParDo.of( - new DoFn() { - @ProcessElement - public void processElement(ProcessContext c) {} - })); - return PDone.in(input.getPipeline()); - } - } -} diff --git a/ingestion/src/main/java/feast/ingestion/transform/metrics/WriteRowMetricsDoFn.java b/ingestion/src/main/java/feast/ingestion/transform/metrics/WriteRowMetricsDoFn.java index db2d1acd6d8..3c7f04a98e7 100644 --- a/ingestion/src/main/java/feast/ingestion/transform/metrics/WriteRowMetricsDoFn.java +++ b/ingestion/src/main/java/feast/ingestion/transform/metrics/WriteRowMetricsDoFn.java @@ -17,27 +17,59 @@ package feast.ingestion.transform.metrics; import com.google.auto.value.AutoValue; +import com.google.protobuf.util.Timestamps; import com.timgroup.statsd.NonBlockingStatsDClient; import com.timgroup.statsd.StatsDClient; -import com.timgroup.statsd.StatsDClientException; -import feast.types.FeatureRowProto.FeatureRow; -import feast.types.FieldProto.Field; -import feast.types.ValueProto.Value.ValCase; +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.proto.types.FieldProto.Field; +import feast.proto.types.ValueProto.Value; +import feast.proto.types.ValueProto.Value.ValCase; +import java.time.Clock; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nullable; import org.apache.beam.sdk.transforms.DoFn; +import org.apache.beam.sdk.values.KV; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics; import org.slf4j.Logger; @AutoValue -public abstract class WriteRowMetricsDoFn extends DoFn { +public abstract class WriteRowMetricsDoFn extends DoFn>, Void> { private static final Logger log = org.slf4j.LoggerFactory.getLogger(WriteRowMetricsDoFn.class); - private final String METRIC_PREFIX = "feast_ingestion"; - private final String STORE_TAG_KEY = "feast_store"; - private final String FEATURE_SET_PROJECT_TAG_KEY = "feast_project_name"; - private final String FEATURE_SET_NAME_TAG_KEY = "feast_featureSet_name"; - private final String FEATURE_SET_VERSION_TAG_KEY = "feast_featureSet_version"; - private final String FEATURE_TAG_KEY = "feast_feature_name"; - private final String INGESTION_JOB_NAME_KEY = "ingestion_job_name"; + public static final String METRIC_PREFIX = "feast_ingestion"; + public static final String STORE_TAG_KEY = "feast_store"; + public static final String FEATURE_SET_PROJECT_TAG_KEY = "feast_project_name"; + public static final String FEATURE_SET_NAME_TAG_KEY = "feast_featureSet_name"; + public static final String FEATURE_SET_VERSION_TAG_KEY = "feast_featureSet_version"; + public static final String FEATURE_TAG_KEY = "feast_feature_name"; + public static final String INGESTION_JOB_NAME_KEY = "ingestion_job_name"; + + public static final String GAUGE_NAME_FEATURE_ROW_LAG_MS_MIN = "feature_row_lag_ms_min"; + public static final String GAUGE_NAME_FEATURE_ROW_LAG_MS_MAX = "feature_row_lag_ms_max"; + public static final String GAUGE_NAME_FEATURE_ROW_LAG_MS_MEAN = "feature_row_lag_ms_mean"; + public static final String GAUGE_NAME_FEATURE_ROW_LAG_MS_PERCENTILE_90 = + "feature_row_lag_ms_percentile_90"; + public static final String GAUGE_NAME_FEATURE_ROW_LAG_MS_PERCENTILE_95 = + "feature_row_lag_ms_percentile_95"; + public static final String GAUGE_NAME_FEATURE_ROW_LAG_MS_PERCENTILE_99 = + "feature_row_lag_ms_percentile_99"; + + public static final String GAUGE_NAME_FEATURE_VALUE_LAG_MS_MIN = "feature_value_lag_ms_min"; + public static final String GAUGE_NAME_FEATURE_VALUE_LAG_MS_MAX = "feature_value_lag_ms_max"; + public static final String GAUGE_NAME_FEATURE_VALUE_LAG_MS_MEAN = "feature_value_lag_ms_mean"; + public static final String GAUGE_NAME_FEATURE_VALUE_LAG_MS_PERCENTILE_90 = + "feature_value_lag_ms_percentile_90"; + public static final String GAUGE_NAME_FEATURE_VALUE_LAG_MS_PERCENTILE_95 = + "feature_value_lag_ms_percentile_95"; + public static final String GAUGE_NAME_FEATURE_VALUE_LAG_MS_PERCENTILE_99 = + "feature_value_lag_ms_percentile_99"; + + public static final String COUNT_NAME_FEATURE_ROW_INGESTED = "feature_row_ingested_count"; + public static final String COUNT_NAME_FEATURE_VALUE_MISSING = "feature_value_missing_count"; public abstract String getStoreName(); @@ -45,6 +77,9 @@ public abstract class WriteRowMetricsDoFn extends DoFn { public abstract int getStatsdPort(); + @Nullable + public abstract Clock getClock(); + public static WriteRowMetricsDoFn create( String newStoreName, String newStatsdHost, int newStatsdPort) { return newBuilder() @@ -69,79 +104,137 @@ public abstract static class Builder { public abstract Builder setStatsdPort(int statsdPort); + /** + * setClock will override the default system clock used to calculate feature row lag. + * + * @param clock Clock instance + */ + public abstract Builder setClock(Clock clock); + public abstract WriteRowMetricsDoFn build(); } @Setup public void setup() { - statsd = new NonBlockingStatsDClient(METRIC_PREFIX, getStatsdHost(), getStatsdPort()); + // Note that exception may be thrown during StatsD client instantiation but no exception + // will be thrown when sending metrics (mimicking the UDP protocol behaviour). + // https://jar-download.com/artifacts/com.datadoghq/java-dogstatsd-client/2.1.1/documentation + // https://github.com/DataDog/java-dogstatsd-client#unix-domain-socket-support + try { + statsd = new NonBlockingStatsDClient(METRIC_PREFIX, getStatsdHost(), getStatsdPort()); + } catch (Exception e) { + log.error("StatsD client cannot be started: " + e.getMessage()); + } } + @SuppressWarnings("DuplicatedCode") @ProcessElement - public void processElement(ProcessContext c) { + public void processElement( + ProcessContext c, @Element KV> featureSetRefToFeatureRows) { + if (statsd == null) { + log.error("StatsD client is null, likely because it encounters an error during setup"); + return; + } - try { - FeatureRow row = c.element(); - long eventTimestamp = com.google.protobuf.util.Timestamps.toMillis(row.getEventTimestamp()); - - String[] split = row.getFeatureSet().split(":"); - String featureSetProject = split[0].split("/")[0]; - String featureSetName = split[0].split("/")[1]; - String featureSetVersion = split[1]; - - statsd.histogram( - "feature_row_lag_ms", - System.currentTimeMillis() - eventTimestamp, - STORE_TAG_KEY + ":" + getStoreName(), - FEATURE_SET_PROJECT_TAG_KEY + ":" + featureSetProject, - FEATURE_SET_NAME_TAG_KEY + ":" + featureSetName, - FEATURE_SET_VERSION_TAG_KEY + ":" + featureSetVersion, - INGESTION_JOB_NAME_KEY + ":" + c.getPipelineOptions().getJobName()); - - statsd.histogram( - "feature_row_event_time_epoch_ms", - eventTimestamp, - STORE_TAG_KEY + ":" + getStoreName(), - FEATURE_SET_PROJECT_TAG_KEY + ":" + featureSetProject, - FEATURE_SET_NAME_TAG_KEY + ":" + featureSetName, - FEATURE_SET_VERSION_TAG_KEY + ":" + featureSetVersion, - INGESTION_JOB_NAME_KEY + ":" + c.getPipelineOptions().getJobName()); - - for (Field field : row.getFieldsList()) { - if (!field.getValue().getValCase().equals(ValCase.VAL_NOT_SET)) { - statsd.histogram( - "feature_value_lag_ms", - System.currentTimeMillis() - eventTimestamp, - STORE_TAG_KEY + ":" + getStoreName(), - FEATURE_SET_PROJECT_TAG_KEY + ":" + featureSetProject, - FEATURE_SET_NAME_TAG_KEY + ":" + featureSetName, - FEATURE_SET_VERSION_TAG_KEY + ":" + featureSetVersion, - FEATURE_TAG_KEY + ":" + field.getName(), - INGESTION_JOB_NAME_KEY + ":" + c.getPipelineOptions().getJobName()); + String featureSetRef = featureSetRefToFeatureRows.getKey(); + if (featureSetRef == null) { + log.error( + "Feature set reference in the feature row is null. Please check the input feature rows from previous steps"); + return; + } + String[] slashSplits = featureSetRef.split("/"); + if (slashSplits.length != 2) { + log.error( + "Skip writing feature row metrics because the feature set reference '{}' does not" + + "follow the required format /:", + featureSetRef); + return; + } + + String featureSetProject = slashSplits[0]; + String featureSetName = slashSplits[1]; + + // featureRowLagStats is stats for feature row lag for feature set "featureSetName" + DescriptiveStatistics featureRowLagStats = new DescriptiveStatistics(); + // featureNameToLagStats is stats for feature lag for all features in feature set + // "featureSetName" + Map featureNameToLagStats = new HashMap<>(); + // featureNameToMissingCount is count for "value_not_set" for all features in feature set + // "featureSetName" + Map featureNameToMissingCount = new HashMap<>(); + + for (FeatureRow featureRow : featureSetRefToFeatureRows.getValue()) { + long currentTime = getClock() == null ? System.currentTimeMillis() : getClock().millis(); + long featureRowLag = currentTime - Timestamps.toMillis(featureRow.getEventTimestamp()); + featureRowLagStats.addValue(featureRowLag); + + for (Field field : featureRow.getFieldsList()) { + String featureName = field.getName(); + Value featureValue = field.getValue(); + if (!featureNameToLagStats.containsKey(featureName)) { + // Ensure map contains the "featureName" key + featureNameToLagStats.put(featureName, new DescriptiveStatistics()); + } + if (!featureNameToMissingCount.containsKey(featureName)) { + // Ensure map contains the "featureName" key + featureNameToMissingCount.put(featureName, 0L); + } + if (featureValue.getValCase().equals(ValCase.VAL_NOT_SET)) { + featureNameToMissingCount.put( + featureName, featureNameToMissingCount.get(featureName) + 1); } else { - statsd.count( - "feature_value_missing_count", - 1, - STORE_TAG_KEY + ":" + getStoreName(), - FEATURE_SET_PROJECT_TAG_KEY + ":" + featureSetProject, - FEATURE_SET_NAME_TAG_KEY + ":" + featureSetName, - FEATURE_SET_VERSION_TAG_KEY + ":" + featureSetVersion, - FEATURE_TAG_KEY + ":" + field.getName(), - INGESTION_JOB_NAME_KEY + ":" + c.getPipelineOptions().getJobName()); + featureNameToLagStats.get(featureName).addValue(featureRowLag); } } + } + String[] tags = { + STORE_TAG_KEY + ":" + getStoreName(), + FEATURE_SET_PROJECT_TAG_KEY + ":" + featureSetProject, + FEATURE_SET_NAME_TAG_KEY + ":" + featureSetName, + INGESTION_JOB_NAME_KEY + ":" + c.getPipelineOptions().getJobName(), + }; + + statsd.count(COUNT_NAME_FEATURE_ROW_INGESTED, featureRowLagStats.getN(), tags); + // DescriptiveStatistics returns invalid NaN value for getMin(), getMax(), ... when there is no + // items in the stats. + if (featureRowLagStats.getN() > 0) { + statsd.gauge(GAUGE_NAME_FEATURE_ROW_LAG_MS_MIN, featureRowLagStats.getMin(), tags); + statsd.gauge(GAUGE_NAME_FEATURE_ROW_LAG_MS_MAX, featureRowLagStats.getMax(), tags); + statsd.gauge(GAUGE_NAME_FEATURE_ROW_LAG_MS_MEAN, featureRowLagStats.getMean(), tags); + statsd.gauge( + GAUGE_NAME_FEATURE_ROW_LAG_MS_PERCENTILE_90, featureRowLagStats.getPercentile(90), tags); + statsd.gauge( + GAUGE_NAME_FEATURE_ROW_LAG_MS_PERCENTILE_95, featureRowLagStats.getPercentile(95), tags); + statsd.gauge( + GAUGE_NAME_FEATURE_ROW_LAG_MS_PERCENTILE_99, featureRowLagStats.getPercentile(99), tags); + } + + for (Entry entry : featureNameToLagStats.entrySet()) { + String featureName = entry.getKey(); + String[] tagsWithFeatureName = ArrayUtils.add(tags, FEATURE_TAG_KEY + ":" + featureName); + DescriptiveStatistics stats = entry.getValue(); + if (stats.getN() > 0) { + statsd.gauge(GAUGE_NAME_FEATURE_VALUE_LAG_MS_MIN, stats.getMin(), tagsWithFeatureName); + statsd.gauge(GAUGE_NAME_FEATURE_VALUE_LAG_MS_MAX, stats.getMax(), tagsWithFeatureName); + statsd.gauge(GAUGE_NAME_FEATURE_VALUE_LAG_MS_MEAN, stats.getMean(), tagsWithFeatureName); + statsd.gauge( + GAUGE_NAME_FEATURE_VALUE_LAG_MS_PERCENTILE_90, + stats.getPercentile(90), + tagsWithFeatureName); + statsd.gauge( + GAUGE_NAME_FEATURE_VALUE_LAG_MS_PERCENTILE_95, + stats.getPercentile(95), + tagsWithFeatureName); + statsd.gauge( + GAUGE_NAME_FEATURE_VALUE_LAG_MS_PERCENTILE_99, + stats.getPercentile(99), + tagsWithFeatureName); + } statsd.count( - "feature_row_ingested_count", - 1, - STORE_TAG_KEY + ":" + getStoreName(), - FEATURE_SET_PROJECT_TAG_KEY + ":" + featureSetProject, - FEATURE_SET_NAME_TAG_KEY + ":" + featureSetName, - FEATURE_SET_VERSION_TAG_KEY + ":" + featureSetVersion, - INGESTION_JOB_NAME_KEY + ":" + c.getPipelineOptions().getJobName()); - - } catch (StatsDClientException e) { - log.warn("Unable to push metrics to server", e); + COUNT_NAME_FEATURE_VALUE_MISSING, + featureNameToMissingCount.get(featureName), + tagsWithFeatureName); } } } diff --git a/ingestion/src/main/java/feast/ingestion/transform/metrics/WriteSuccessMetricsTransform.java b/ingestion/src/main/java/feast/ingestion/transform/metrics/WriteSuccessMetricsTransform.java new file mode 100644 index 00000000000..e75debf4a93 --- /dev/null +++ b/ingestion/src/main/java/feast/ingestion/transform/metrics/WriteSuccessMetricsTransform.java @@ -0,0 +1,118 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2019 The Feast Authors + * + * 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 feast.ingestion.transform.metrics; + +import com.google.auto.value.AutoValue; +import feast.ingestion.options.ImportOptions; +import feast.proto.types.FeatureRowProto.FeatureRow; +import org.apache.beam.sdk.metrics.Counter; +import org.apache.beam.sdk.metrics.Metrics; +import org.apache.beam.sdk.transforms.*; +import org.apache.beam.sdk.transforms.windowing.FixedWindows; +import org.apache.beam.sdk.transforms.windowing.Window; +import org.apache.beam.sdk.values.KV; +import org.apache.beam.sdk.values.PCollection; +import org.apache.beam.sdk.values.PDone; +import org.apache.beam.sdk.values.TypeDescriptors; +import org.joda.time.Duration; + +@AutoValue +public abstract class WriteSuccessMetricsTransform + extends PTransform, PDone> { + + public static final String METRIC_NAMESPACE = "WriteToStoreSuccess"; + public static final String ELEMENTS_WRITTEN_METRIC = "elements_written"; + private static final Counter elementsWritten = + Metrics.counter(METRIC_NAMESPACE, ELEMENTS_WRITTEN_METRIC); + + public abstract String getStoreName(); + + public static WriteSuccessMetricsTransform create(String storeName) { + return new AutoValue_WriteSuccessMetricsTransform(storeName); + } + + @Override + public PDone expand(PCollection input) { + ImportOptions options = input.getPipeline().getOptions().as(ImportOptions.class); + + input.apply( + "IncrementSuccessfulWriteToStoreElementsWrittenCounter", + MapElements.into(TypeDescriptors.booleans()) + .via( + (FeatureRow row) -> { + elementsWritten.inc(); + return true; + })); + + switch (options.getMetricsExporterType()) { + case "statsd": + + // Fixed window is applied so the metric collector will not be overwhelmed with the metrics + // data. For validation, only summaries of the values are usually required vs the actual + // values. + PCollection>> validRowsGroupedByRef = + input + .apply( + "FixedWindow", + Window.into( + FixedWindows.of( + Duration.standardSeconds( + options.getWindowSizeInSecForFeatureValueMetric())))) + .apply( + "ConvertToKV_FeatureSetRefToFeatureRow", + ParDo.of( + new DoFn>() { + @ProcessElement + public void processElement( + ProcessContext c, @Element FeatureRow featureRow) { + c.output(KV.of(featureRow.getFeatureSet(), featureRow)); + } + })) + .apply("GroupByFeatureSetRef", GroupByKey.create()); + + validRowsGroupedByRef.apply( + "WriteRowMetrics", + ParDo.of( + WriteRowMetricsDoFn.newBuilder() + .setStatsdHost(options.getStatsdHost()) + .setStatsdPort(options.getStatsdPort()) + .setStoreName(getStoreName()) + .build())); + + validRowsGroupedByRef.apply( + "WriteFeatureValueMetrics", + ParDo.of( + WriteFeatureValueMetricsDoFn.newBuilder() + .setStatsdHost(options.getStatsdHost()) + .setStatsdPort(options.getStatsdPort()) + .setStoreName(getStoreName()) + .build())); + + return PDone.in(input.getPipeline()); + case "none": + default: + input.apply( + "Noop", + ParDo.of( + new DoFn() { + @ProcessElement + public void processElement(ProcessContext c) {} + })); + return PDone.in(input.getPipeline()); + } + } +} diff --git a/ingestion/src/main/java/feast/ingestion/utils/SpecUtil.java b/ingestion/src/main/java/feast/ingestion/utils/SpecUtil.java index 9163c5b2d6f..a1356584b3f 100644 --- a/ingestion/src/main/java/feast/ingestion/utils/SpecUtil.java +++ b/ingestion/src/main/java/feast/ingestion/utils/SpecUtil.java @@ -18,13 +18,13 @@ import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.util.JsonFormat; -import feast.core.FeatureSetProto.EntitySpec; -import feast.core.FeatureSetProto.FeatureSet; -import feast.core.FeatureSetProto.FeatureSetSpec; -import feast.core.FeatureSetProto.FeatureSpec; -import feast.core.StoreProto.Store; -import feast.core.StoreProto.Store.Subscription; import feast.ingestion.values.Field; +import feast.proto.core.FeatureSetProto.EntitySpec; +import feast.proto.core.FeatureSetProto.FeatureSet; +import feast.proto.core.FeatureSetProto.FeatureSetSpec; +import feast.proto.core.FeatureSetProto.FeatureSpec; +import feast.proto.core.StoreProto.Store; +import feast.proto.core.StoreProto.Store.Subscription; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -33,9 +33,8 @@ public class SpecUtil { - public static String getFeatureSetReference(FeatureSet featureSet) { - FeatureSetSpec spec = featureSet.getSpec(); - return String.format("%s/%s:%d", spec.getProject(), spec.getName(), spec.getVersion()); + public static String getFeatureSetReference(FeatureSetSpec featureSetSpec) { + return String.format("%s/%s", featureSetSpec.getProject(), featureSetSpec.getName()); } /** Get only feature set specs that matches the subscription */ @@ -45,28 +44,17 @@ public static List getSubscribedFeatureSets( for (FeatureSet featureSet : featureSets) { for (Subscription sub : subscriptions) { // If configuration missing, fail - if (sub.getProject().isEmpty() || sub.getName().isEmpty() || sub.getVersion().isEmpty()) { + if (sub.getProject().isEmpty() || sub.getName().isEmpty()) { throw new IllegalArgumentException( String.format("Subscription is missing arguments: %s", sub.toString())); } // If all wildcards, subscribe to everything - if (sub.getProject().equals("*") - || sub.getName().equals("*") - || sub.getVersion().equals("*")) { + if (sub.getProject().equals("*") || sub.getName().equals("*")) { subscribed.add(featureSet); break; } - // If all wildcards, subscribe to everything - if (sub.getProject().equals("*") - && (!sub.getName().equals("*") || !sub.getVersion().equals("*"))) { - throw new IllegalArgumentException( - String.format( - "Subscription cannot have feature set name and/or version set if project is not defined: %s", - sub.toString())); - } - // Match project name if (!featureSet.getSpec().getProject().equals(sub.getProject())) { continue; @@ -83,26 +71,7 @@ public static List getSubscribedFeatureSets( if (!pattern.matcher(featureSet.getSpec().getName()).matches()) { continue; } - - // If version is '*', match all - if (sub.getVersion().equals("*")) { - subscribed.add(featureSet); - break; - } else if (sub.getVersion().equals("latest")) { - // if version is "latest" - throw new RuntimeException( - String.format( - "Support for latest feature set subscription has not been implemented yet: %s", - sub.toString())); - - } else { - // If a specific version, match that version alone - int version = Integer.parseInt(sub.getVersion()); - if (featureSet.getSpec().getVersion() == version) { - subscribed.add(featureSet); - break; - } - } + subscribed.add(featureSet); } } return subscribed; diff --git a/ingestion/src/main/java/feast/ingestion/utils/StoreUtil.java b/ingestion/src/main/java/feast/ingestion/utils/StoreUtil.java index 7af98fb8f00..54323869cf8 100644 --- a/ingestion/src/main/java/feast/ingestion/utils/StoreUtil.java +++ b/ingestion/src/main/java/feast/ingestion/utils/StoreUtil.java @@ -16,41 +16,20 @@ */ package feast.ingestion.utils; -import static feast.types.ValueProto.ValueType; +import static feast.proto.types.ValueProto.ValueType; -import com.google.cloud.bigquery.BigQuery; -import com.google.cloud.bigquery.BigQueryOptions; -import com.google.cloud.bigquery.DatasetId; -import com.google.cloud.bigquery.DatasetInfo; -import com.google.cloud.bigquery.Field; -import com.google.cloud.bigquery.Field.Builder; -import com.google.cloud.bigquery.Field.Mode; -import com.google.cloud.bigquery.Schema; import com.google.cloud.bigquery.StandardSQLTypeName; -import com.google.cloud.bigquery.StandardTableDefinition; -import com.google.cloud.bigquery.Table; -import com.google.cloud.bigquery.TableDefinition; -import com.google.cloud.bigquery.TableId; -import com.google.cloud.bigquery.TableInfo; -import com.google.cloud.bigquery.TimePartitioning; -import com.google.cloud.bigquery.TimePartitioning.Type; -import com.google.common.collect.ImmutableMap; -import feast.core.FeatureSetProto.EntitySpec; -import feast.core.FeatureSetProto.FeatureSet; -import feast.core.FeatureSetProto.FeatureSetSpec; -import feast.core.FeatureSetProto.FeatureSpec; -import feast.core.StoreProto.Store; -import feast.core.StoreProto.Store.RedisConfig; -import feast.core.StoreProto.Store.StoreType; -import feast.types.ValueProto.ValueType.Enum; -import java.util.ArrayList; +import feast.proto.core.FeatureSetProto.FeatureSetSpec; +import feast.proto.core.StoreProto.Store; +import feast.proto.core.StoreProto.Store.StoreType; +import feast.proto.types.ValueProto.ValueType.Enum; +import feast.storage.api.writer.FeatureSink; +import feast.storage.connectors.bigquery.writer.BigQueryFeatureSink; +import feast.storage.connectors.redis.writer.RedisFeatureSink; +import feast.storage.connectors.rediscluster.writer.RedisClusterFeatureSink; import java.util.HashMap; -import java.util.List; import java.util.Map; -import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; -import redis.clients.jedis.JedisPool; -import redis.clients.jedis.exceptions.JedisConnectionException; // TODO: Create partitioned table by default @@ -100,154 +79,18 @@ public class StoreUtil { VALUE_TYPE_TO_STANDARD_SQL_TYPE.put(Enum.BOOL_LIST, StandardSQLTypeName.BOOL); } - public static void setupStore(Store store, FeatureSet featureSet) { + public static FeatureSink getFeatureSink( + Store store, Map featureSetSpecs) { StoreType storeType = store.getType(); switch (storeType) { + case REDIS_CLUSTER: + return RedisClusterFeatureSink.fromConfig(store.getRedisClusterConfig(), featureSetSpecs); case REDIS: - StoreUtil.checkRedisConnection(store.getRedisConfig()); - break; + return RedisFeatureSink.fromConfig(store.getRedisConfig(), featureSetSpecs); case BIGQUERY: - StoreUtil.setupBigQuery( - featureSet, - store.getBigqueryConfig().getProjectId(), - store.getBigqueryConfig().getDatasetId(), - BigQueryOptions.getDefaultInstance().getService()); - break; + return BigQueryFeatureSink.fromConfig(store.getBigqueryConfig(), featureSetSpecs); default: - log.warn("Store type '{}' is unsupported", storeType); - break; + throw new RuntimeException(String.format("Store type '%s' is unsupported", storeType)); } } - - @SuppressWarnings("DuplicatedCode") - public static TableDefinition createBigQueryTableDefinition(FeatureSetSpec featureSetSpec) { - List fields = new ArrayList<>(); - log.info("Table will have the following fields:"); - - for (EntitySpec entitySpec : featureSetSpec.getEntitiesList()) { - Builder builder = - Field.newBuilder( - entitySpec.getName(), VALUE_TYPE_TO_STANDARD_SQL_TYPE.get(entitySpec.getValueType())); - if (entitySpec.getValueType().name().toLowerCase().endsWith("_list")) { - builder.setMode(Mode.REPEATED); - } - Field field = builder.build(); - log.info("- {}", field.toString()); - fields.add(field); - } - for (FeatureSpec featureSpec : featureSetSpec.getFeaturesList()) { - Builder builder = - Field.newBuilder( - featureSpec.getName(), - VALUE_TYPE_TO_STANDARD_SQL_TYPE.get(featureSpec.getValueType())); - if (featureSpec.getValueType().name().toLowerCase().endsWith("_list")) { - builder.setMode(Mode.REPEATED); - } - Field field = builder.build(); - log.info("- {}", field.toString()); - fields.add(field); - } - - // Refer to protos/feast/core/Store.proto for reserved fields in BigQuery. - Map> - reservedFieldNameToPairOfStandardSQLTypeAndDescription = - ImmutableMap.of( - "event_timestamp", - Pair.of(StandardSQLTypeName.TIMESTAMP, BIGQUERY_EVENT_TIMESTAMP_FIELD_DESCRIPTION), - "created_timestamp", - Pair.of( - StandardSQLTypeName.TIMESTAMP, BIGQUERY_CREATED_TIMESTAMP_FIELD_DESCRIPTION), - "job_id", - Pair.of(StandardSQLTypeName.STRING, BIGQUERY_JOB_ID_FIELD_DESCRIPTION)); - for (Map.Entry> entry : - reservedFieldNameToPairOfStandardSQLTypeAndDescription.entrySet()) { - Field field = - Field.newBuilder(entry.getKey(), entry.getValue().getLeft()) - .setDescription(entry.getValue().getRight()) - .build(); - log.info("- {}", field.toString()); - fields.add(field); - } - - TimePartitioning timePartitioning = - TimePartitioning.newBuilder(Type.DAY).setField("event_timestamp").build(); - log.info("Table partitioning: " + timePartitioning.toString()); - - return StandardTableDefinition.newBuilder() - .setTimePartitioning(timePartitioning) - .setSchema(Schema.of(fields)) - .build(); - } - - /** - * This method ensures that, given a FeatureSetSpec object, the relevant BigQuery table is created - * with the correct schema. - * - *

Refer to protos/feast/core/Store.proto for the derivation of the table name and schema from - * a FeatureSetSpec object. - * - * @param featureSet FeatureSet object - * @param bigqueryProjectId BigQuery project id - * @param bigqueryDatasetId BigQuery dataset id - * @param bigquery BigQuery service object - */ - public static void setupBigQuery( - FeatureSet featureSet, - String bigqueryProjectId, - String bigqueryDatasetId, - BigQuery bigquery) { - - FeatureSetSpec featureSetSpec = featureSet.getSpec(); - // Ensure BigQuery dataset exists. - DatasetId datasetId = DatasetId.of(bigqueryProjectId, bigqueryDatasetId); - if (bigquery.getDataset(datasetId) == null) { - log.info("Creating dataset '{}' in project '{}'", datasetId.getDataset(), bigqueryProjectId); - bigquery.create(DatasetInfo.of(datasetId)); - } - - String tableName = - String.format( - "%s_%s_v%d", - featureSetSpec.getProject(), featureSetSpec.getName(), featureSetSpec.getVersion()) - .replaceAll("-", "_"); - TableId tableId = TableId.of(bigqueryProjectId, datasetId.getDataset(), tableName); - - // Return if there is an existing table - Table table = bigquery.getTable(tableId); - if (table != null) { - log.info( - "Writing to existing BigQuery table '{}:{}.{}'", - bigqueryProjectId, - datasetId.getDataset(), - tableName); - return; - } - - log.info( - "Creating table '{}' in dataset '{}' in project '{}'", - tableId.getTable(), - datasetId.getDataset(), - bigqueryProjectId); - TableDefinition tableDefinition = createBigQueryTableDefinition(featureSet.getSpec()); - TableInfo tableInfo = TableInfo.of(tableId, tableDefinition); - bigquery.create(tableInfo); - } - - /** - * Ensure Redis is accessible, else throw a RuntimeException. - * - * @param redisConfig Plase refer to feast.core.Store proto - */ - public static void checkRedisConnection(RedisConfig redisConfig) { - JedisPool jedisPool = new JedisPool(redisConfig.getHost(), redisConfig.getPort()); - try { - jedisPool.getResource(); - } catch (JedisConnectionException e) { - throw new RuntimeException( - String.format( - "Failed to connect to Redis at host: '%s' port: '%d'. Please check that your Redis is running and accessible from Feast.", - redisConfig.getHost(), redisConfig.getPort())); - } - jedisPool.close(); - } } diff --git a/ingestion/src/main/java/feast/ingestion/values/FeatureSet.java b/ingestion/src/main/java/feast/ingestion/values/FeatureSet.java index bf07bcec966..f49ab2f98a4 100644 --- a/ingestion/src/main/java/feast/ingestion/values/FeatureSet.java +++ b/ingestion/src/main/java/feast/ingestion/values/FeatureSet.java @@ -19,12 +19,12 @@ import static feast.ingestion.utils.SpecUtil.getFeatureSetReference; import static feast.ingestion.utils.SpecUtil.getFieldsByName; -import feast.core.FeatureSetProto; +import feast.proto.core.FeatureSetProto; import java.io.Serializable; import java.util.Map; /** - * This class represents {@link feast.core.FeatureSetProto.FeatureSetSpec} but contains fields + * This class represents {@link feast.proto.core.FeatureSetProto.FeatureSetSpec} but contains fields * directly accessible by name for feature validation purposes. * *

The use for this class is mainly for validating the Fields in FeatureRow. @@ -34,9 +34,9 @@ public class FeatureSet implements Serializable { private final Map fields; - public FeatureSet(FeatureSetProto.FeatureSet featureSet) { - this.reference = getFeatureSetReference(featureSet); - this.fields = getFieldsByName(featureSet.getSpec()); + public FeatureSet(FeatureSetProto.FeatureSetSpec featureSetSpec) { + this.reference = getFeatureSetReference(featureSetSpec); + this.fields = getFieldsByName(featureSetSpec); } public String getReference() { diff --git a/ingestion/src/main/java/feast/ingestion/values/Field.java b/ingestion/src/main/java/feast/ingestion/values/Field.java index 9b4f570d7e6..79cb83e76ad 100644 --- a/ingestion/src/main/java/feast/ingestion/values/Field.java +++ b/ingestion/src/main/java/feast/ingestion/values/Field.java @@ -16,13 +16,13 @@ */ package feast.ingestion.values; -import feast.types.ValueProto.ValueType; +import feast.proto.types.ValueProto.ValueType; import java.io.Serializable; import org.apache.beam.sdk.coders.AvroCoder; import org.apache.beam.sdk.coders.DefaultCoder; /** - * Field class represents {@link feast.types.FieldProto.Field} but without value. + * Field class represents {@link feast.proto.types.FieldProto.Field} but without value. * *

The use for this class is mainly for validating the Fields in FeatureRow. */ diff --git a/ingestion/src/main/java/feast/retry/BackOffExecutor.java b/ingestion/src/main/java/feast/retry/BackOffExecutor.java deleted file mode 100644 index 7e38a3cf706..00000000000 --- a/ingestion/src/main/java/feast/retry/BackOffExecutor.java +++ /dev/null @@ -1,38 +0,0 @@ -package feast.retry; - -import org.apache.beam.sdk.util.BackOff; -import org.apache.beam.sdk.util.BackOffUtils; -import org.apache.beam.sdk.util.FluentBackoff; -import org.apache.beam.sdk.util.Sleeper; -import org.joda.time.Duration; - -import java.io.IOException; -import java.io.Serializable; - -public class BackOffExecutor implements Serializable { - - private static FluentBackoff backoff; - - public BackOffExecutor(Integer maxRetries, Duration initialBackOff) { - backoff = FluentBackoff.DEFAULT - .withMaxRetries(maxRetries) - .withInitialBackoff(initialBackOff); - } - - public void execute(Retriable retriable) throws Exception { - Sleeper sleeper = Sleeper.DEFAULT; - BackOff backOff = backoff.backoff(); - while(true) { - try { - retriable.execute(); - break; - } catch (Exception e) { - if(retriable.isExceptionRetriable(e) && BackOffUtils.next(sleeper, backOff)) { - retriable.cleanUpAfterFailure(); - } else { - throw e; - } - } - } - } -} diff --git a/ingestion/src/main/java/feast/retry/Retriable.java b/ingestion/src/main/java/feast/retry/Retriable.java deleted file mode 100644 index 8fd76fedbb1..00000000000 --- a/ingestion/src/main/java/feast/retry/Retriable.java +++ /dev/null @@ -1,7 +0,0 @@ -package feast.retry; - -public interface Retriable { - void execute(); - Boolean isExceptionRetriable(Exception e); - void cleanUpAfterFailure(); -} diff --git a/ingestion/src/main/java/feast/store/serving/redis/FeatureRowToRedisMutationDoFn.java b/ingestion/src/main/java/feast/store/serving/redis/FeatureRowToRedisMutationDoFn.java deleted file mode 100644 index 27cca2ffb2e..00000000000 --- a/ingestion/src/main/java/feast/store/serving/redis/FeatureRowToRedisMutationDoFn.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright 2018-2019 The Feast Authors - * - * 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 feast.store.serving.redis; - -import feast.core.FeatureSetProto.EntitySpec; -import feast.core.FeatureSetProto.FeatureSet; -import feast.storage.RedisProto.RedisKey; -import feast.storage.RedisProto.RedisKey.Builder; -import feast.store.serving.redis.RedisCustomIO.Method; -import feast.store.serving.redis.RedisCustomIO.RedisMutation; -import feast.types.FeatureRowProto.FeatureRow; -import feast.types.FieldProto.Field; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; -import org.apache.beam.sdk.transforms.DoFn; -import org.slf4j.Logger; - -public class FeatureRowToRedisMutationDoFn extends DoFn { - - private static final Logger log = - org.slf4j.LoggerFactory.getLogger(FeatureRowToRedisMutationDoFn.class); - private Map featureSets; - - public FeatureRowToRedisMutationDoFn(Map featureSets) { - this.featureSets = featureSets; - } - - private RedisKey getKey(FeatureRow featureRow) { - FeatureSet featureSet = featureSets.get(featureRow.getFeatureSet()); - Set entityNames = - featureSet.getSpec().getEntitiesList().stream() - .map(EntitySpec::getName) - .collect(Collectors.toSet()); - - Builder redisKeyBuilder = RedisKey.newBuilder().setFeatureSet(featureRow.getFeatureSet()); - for (Field field : featureRow.getFieldsList()) { - if (entityNames.contains(field.getName())) { - redisKeyBuilder.addEntities(field); - } - } - return redisKeyBuilder.build(); - } - - /** Output a redis mutation object for every feature in the feature row. */ - @ProcessElement - public void processElement(ProcessContext context) { - FeatureRow featureRow = context.element(); - try { - RedisKey key = getKey(featureRow); - RedisMutation redisMutation = - new RedisMutation(Method.SET, key.toByteArray(), featureRow.toByteArray(), null, null); - context.output(redisMutation); - } catch (Exception e) { - log.error(e.getMessage(), e); - } - } -} diff --git a/ingestion/src/main/java/feast/store/serving/redis/RedisCustomIO.java b/ingestion/src/main/java/feast/store/serving/redis/RedisCustomIO.java deleted file mode 100644 index 20afc43d76c..00000000000 --- a/ingestion/src/main/java/feast/store/serving/redis/RedisCustomIO.java +++ /dev/null @@ -1,339 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright 2018-2019 The Feast Authors - * - * 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 feast.store.serving.redis; - -import feast.core.StoreProto; -import feast.ingestion.values.FailedElement; -import feast.retry.BackOffExecutor; -import feast.retry.Retriable; -import org.apache.avro.reflect.Nullable; -import org.apache.beam.sdk.coders.AvroCoder; -import org.apache.beam.sdk.coders.DefaultCoder; -import org.apache.beam.sdk.transforms.DoFn; -import org.apache.beam.sdk.transforms.PTransform; -import org.apache.beam.sdk.transforms.ParDo; -import org.apache.beam.sdk.transforms.windowing.GlobalWindow; -import org.apache.beam.sdk.values.PCollection; -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.joda.time.Duration; -import org.joda.time.Instant; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.Pipeline; -import redis.clients.jedis.Response; -import redis.clients.jedis.exceptions.JedisConnectionException; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -public class RedisCustomIO { - - private static final int DEFAULT_BATCH_SIZE = 1000; - private static final int DEFAULT_TIMEOUT = 2000; - - private static final Logger log = LoggerFactory.getLogger(RedisCustomIO.class); - - private RedisCustomIO() {} - - public static Write write(StoreProto.Store.RedisConfig redisConfig) { - return new Write(redisConfig); - } - - public enum Method { - - /** - * Use APPEND command. If key already exists and is a string, this command appends the value at - * the end of the string. - */ - APPEND, - - /** Use SET command. If key already holds a value, it is overwritten. */ - SET, - - /** - * Use LPUSH command. Insert value at the head of the list stored at key. If key does not exist, - * it is created as empty list before performing the push operations. When key holds a value - * that is not a list, an error is returned. - */ - LPUSH, - - /** - * Use RPUSH command. Insert value at the tail of the list stored at key. If key does not exist, - * it is created as empty list before performing the push operations. When key holds a value - * that is not a list, an error is returned. - */ - RPUSH, - - /** - * Use SADD command. Insert value into a set with a defined key. If key does not exist, it is - * created as empty set before performing the add operations. When key holds a value that is not - * a set, an error is returned. - */ - SADD, - - /** - * Use ZADD command. Adds all the specified members with the specified scores to the sorted set - * stored at key. It is possible to specify multiple score / member pairs. If a specified member - * is already a member of the sorted set, the score is updated and the element reinserted at the - * right position to ensure the correct ordering. - */ - ZADD - } - - @DefaultCoder(AvroCoder.class) - public static class RedisMutation { - - private Method method; - private byte[] key; - private byte[] value; - @Nullable private Long expiryMillis; - @Nullable private Long score; - - public RedisMutation() {} - - public RedisMutation( - Method method, - byte[] key, - byte[] value, - @Nullable Long expiryMillis, - @Nullable Long score) { - this.method = method; - this.key = key; - this.value = value; - this.expiryMillis = expiryMillis; - this.score = score; - } - - public Method getMethod() { - return method; - } - - public void setMethod(Method method) { - this.method = method; - } - - public byte[] getKey() { - return key; - } - - public void setKey(byte[] key) { - this.key = key; - } - - public byte[] getValue() { - return value; - } - - public void setValue(byte[] value) { - this.value = value; - } - - @Nullable - public Long getExpiryMillis() { - return expiryMillis; - } - - public void setExpiryMillis(@Nullable Long expiryMillis) { - this.expiryMillis = expiryMillis; - } - - @Nullable - public Long getScore() { - return score; - } - - public void setScore(@Nullable Long score) { - this.score = score; - } - } - - /** ServingStoreWrite data to a Redis server. */ - public static class Write extends PTransform, PCollection> { - - private WriteDoFn dofn; - - private Write(StoreProto.Store.RedisConfig redisConfig) { - this.dofn = new WriteDoFn(redisConfig); - } - - public Write withBatchSize(int batchSize) { - this.dofn.withBatchSize(batchSize); - return this; - } - - public Write withTimeout(int timeout) { - this.dofn.withTimeout(timeout); - return this; - } - - @Override - public PCollection expand(PCollection input) { - return input.apply(ParDo.of(dofn)); - } - - public static class WriteDoFn extends DoFn { - - private final String host; - private final int port; - private final BackOffExecutor backOffExecutor; - private final List mutations = new ArrayList<>(); - - private Jedis jedis; - private Pipeline pipeline; - private int batchSize = DEFAULT_BATCH_SIZE; - private int timeout = DEFAULT_TIMEOUT; - - WriteDoFn(StoreProto.Store.RedisConfig redisConfig) { - this.host = redisConfig.getHost(); - this.port = redisConfig.getPort(); - long backoffMs = redisConfig.getInitialBackoffMs() > 0 ? redisConfig.getInitialBackoffMs() : 1; - this.backOffExecutor = new BackOffExecutor(redisConfig.getMaxRetries(), - Duration.millis(backoffMs)); - } - - public WriteDoFn withBatchSize(int batchSize) { - if (batchSize > 0) { - this.batchSize = batchSize; - } - return this; - } - - public WriteDoFn withTimeout(int timeout) { - if (timeout > 0) { - this.timeout = timeout; - } - return this; - } - - @Setup - public void setup() { - jedis = new Jedis(host, port, timeout); - } - - @StartBundle - public void startBundle() { - mutations.clear(); - pipeline = jedis.pipelined(); - } - - private void executeBatch() throws Exception { - backOffExecutor.execute(new Retriable() { - @Override - public void execute() { - pipeline.multi(); - mutations.forEach(mutation -> { - writeRecord(mutation); - if (mutation.getExpiryMillis() != null && mutation.getExpiryMillis() > 0) { - pipeline.pexpire(mutation.getKey(), mutation.getExpiryMillis()); - } - }); - pipeline.exec(); - pipeline.sync(); - mutations.clear(); - } - - @Override - public Boolean isExceptionRetriable(Exception e) { - return e instanceof JedisConnectionException; - } - - @Override - public void cleanUpAfterFailure() { - try { - pipeline.close(); - } catch (IOException e) { - log.error(String.format("Error while closing pipeline: %s", e.getMessage())); - } - jedis = new Jedis(host, port, timeout); - pipeline = jedis.pipelined(); - } - }); - } - - private FailedElement toFailedElement(RedisMutation mutation, Exception exception, String jobName) { - return FailedElement.newBuilder() - .setJobName(jobName) - .setTransformName("RedisCustomIO") - .setPayload(mutation.getValue().toString()) - .setErrorMessage(exception.getMessage()) - .setStackTrace(ExceptionUtils.getStackTrace(exception)) - .build(); - } - - @ProcessElement - public void processElement(ProcessContext context) { - RedisMutation mutation = context.element(); - mutations.add(mutation); - if (mutations.size() >= batchSize) { - try { - executeBatch(); - } catch (Exception e) { - mutations.forEach(failedMutation -> { - FailedElement failedElement = toFailedElement( - failedMutation, e, context.getPipelineOptions().getJobName()); - context.output(failedElement); - }); - mutations.clear(); - } - } - } - - private Response writeRecord(RedisMutation mutation) { - switch (mutation.getMethod()) { - case APPEND: - return pipeline.append(mutation.getKey(), mutation.getValue()); - case SET: - return pipeline.set(mutation.getKey(), mutation.getValue()); - case LPUSH: - return pipeline.lpush(mutation.getKey(), mutation.getValue()); - case RPUSH: - return pipeline.rpush(mutation.getKey(), mutation.getValue()); - case SADD: - return pipeline.sadd(mutation.getKey(), mutation.getValue()); - case ZADD: - return pipeline.zadd(mutation.getKey(), mutation.getScore(), mutation.getValue()); - default: - throw new UnsupportedOperationException( - String.format("Not implemented writing records for %s", mutation.getMethod())); - } - } - - @FinishBundle - public void finishBundle(FinishBundleContext context) throws IOException, InterruptedException { - if(mutations.size() > 0) { - try { - executeBatch(); - } catch (Exception e) { - mutations.forEach(failedMutation -> { - FailedElement failedElement = toFailedElement( - failedMutation, e, context.getPipelineOptions().getJobName()); - context.output(failedElement, Instant.now(), GlobalWindow.INSTANCE); - }); - mutations.clear(); - } - } - } - - @Teardown - public void teardown() { - jedis.close(); - } - } - } -} diff --git a/ingestion/src/main/proto/feast b/ingestion/src/main/proto/feast deleted file mode 120000 index d520da9126b..00000000000 --- a/ingestion/src/main/proto/feast +++ /dev/null @@ -1 +0,0 @@ -../../../../protos/feast \ No newline at end of file diff --git a/ingestion/src/main/proto/feast_ingestion/types/CoalesceAccum.proto b/ingestion/src/main/proto/feast_ingestion/types/CoalesceAccum.proto deleted file mode 100644 index cb64dd715f6..00000000000 --- a/ingestion/src/main/proto/feast_ingestion/types/CoalesceAccum.proto +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2018 The Feast Authors - * - * 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"; - -import "google/protobuf/timestamp.proto"; -import "feast/types/Field.proto"; - -option java_package = "feast_ingestion.types"; -option java_outer_classname = "CoalesceAccumProto"; - -// Accumlator for merging feature rows. -message CoalesceAccum { - string entityKey = 1; - google.protobuf.Timestamp eventTimestamp = 3; - string entityName = 4; - - map features = 6; - // map of features to their counter values when they were last added to accumulator - map featureMarks = 7; - int64 counter = 8; -} \ No newline at end of file diff --git a/ingestion/src/main/proto/third_party b/ingestion/src/main/proto/third_party deleted file mode 120000 index 363d20598e6..00000000000 --- a/ingestion/src/main/proto/third_party +++ /dev/null @@ -1 +0,0 @@ -../../../../protos/third_party \ No newline at end of file diff --git a/ingestion/src/test/java/feast/ToOrderedFeatureRows.java b/ingestion/src/test/java/feast/ToOrderedFeatureRows.java index db552693f98..35453f0754f 100644 --- a/ingestion/src/test/java/feast/ToOrderedFeatureRows.java +++ b/ingestion/src/test/java/feast/ToOrderedFeatureRows.java @@ -18,9 +18,9 @@ import com.google.common.collect.Lists; import com.google.common.primitives.UnsignedBytes; -import feast.types.FeatureRowExtendedProto.FeatureRowExtended; -import feast.types.FeatureRowProto.FeatureRow; -import feast.types.FieldProto.Field; +import feast.proto.types.FeatureRowExtendedProto.FeatureRowExtended; +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.proto.types.FieldProto.Field; import java.util.List; import org.apache.beam.sdk.transforms.MapElements; import org.apache.beam.sdk.transforms.PTransform; diff --git a/ingestion/src/test/java/feast/ingestion/ImportJobTest.java b/ingestion/src/test/java/feast/ingestion/ImportJobTest.java index 290b38dabee..39e42963782 100644 --- a/ingestion/src/test/java/feast/ingestion/ImportJobTest.java +++ b/ingestion/src/test/java/feast/ingestion/ImportJobTest.java @@ -19,24 +19,31 @@ import com.google.common.io.Files; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.util.JsonFormat; -import feast.core.FeatureSetProto.EntitySpec; -import feast.core.FeatureSetProto.FeatureSet; -import feast.core.FeatureSetProto.FeatureSetSpec; -import feast.core.FeatureSetProto.FeatureSpec; -import feast.core.SourceProto.KafkaSourceConfig; -import feast.core.SourceProto.Source; -import feast.core.SourceProto.SourceType; -import feast.core.StoreProto.Store; -import feast.core.StoreProto.Store.RedisConfig; -import feast.core.StoreProto.Store.StoreType; -import feast.core.StoreProto.Store.Subscription; +import feast.ingestion.options.BZip2Compressor; import feast.ingestion.options.ImportOptions; -import feast.storage.RedisProto.RedisKey; +import feast.proto.core.FeatureSetProto.EntitySpec; +import feast.proto.core.FeatureSetProto.FeatureSet; +import feast.proto.core.FeatureSetProto.FeatureSetSpec; +import feast.proto.core.FeatureSetProto.FeatureSpec; +import feast.proto.core.SourceProto.KafkaSourceConfig; +import feast.proto.core.SourceProto.Source; +import feast.proto.core.SourceProto.SourceType; +import feast.proto.core.StoreProto.Store; +import feast.proto.core.StoreProto.Store.RedisConfig; +import feast.proto.core.StoreProto.Store.StoreType; +import feast.proto.core.StoreProto.Store.Subscription; +import feast.proto.storage.RedisProto.RedisKey; +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.proto.types.FieldProto; +import feast.proto.types.ValueProto.ValueType.Enum; import feast.test.TestUtil; import feast.test.TestUtil.LocalKafka; import feast.test.TestUtil.LocalRedis; -import feast.types.FeatureRowProto.FeatureRow; -import feast.types.ValueProto.ValueType.Enum; +import io.lettuce.core.RedisClient; +import io.lettuce.core.RedisURI; +import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.core.api.sync.RedisCommands; +import io.lettuce.core.codec.ByteArrayCodec; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -44,6 +51,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import java.util.stream.IntStream; import org.apache.beam.sdk.PipelineResult; import org.apache.beam.sdk.PipelineResult.State; @@ -56,7 +64,6 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import redis.clients.jedis.Jedis; public class ImportJobTest { @@ -113,7 +120,6 @@ public void runPipeline_ShouldWriteToRedisCorrectlyGivenValidSpecAndFeatureRow() FeatureSetSpec spec = FeatureSetSpec.newBuilder() .setName("feature_set") - .setVersion(3) .setProject("myproject") .addEntities( EntitySpec.newBuilder() @@ -157,17 +163,20 @@ public void runPipeline_ShouldWriteToRedisCorrectlyGivenValidSpecAndFeatureRow() Subscription.newBuilder() .setProject(spec.getProject()) .setName(spec.getName()) - .setVersion(String.valueOf(spec.getVersion())) .build()) .build(); ImportOptions options = PipelineOptionsFactory.create().as(ImportOptions.class); - options.setFeatureSetJson( - Collections.singletonList( - JsonFormat.printer().omittingInsignificantWhitespace().print(featureSet.getSpec()))); - options.setStoreJson( - Collections.singletonList( - JsonFormat.printer().omittingInsignificantWhitespace().print(redis))); + BZip2Compressor compressor = + new BZip2Compressor<>( + option -> { + JsonFormat.Printer printer = + JsonFormat.printer().omittingInsignificantWhitespace().printingEnumsAsInts(); + return printer.print(option).getBytes(); + }); + options.setFeatureSetJson(compressor.compress(spec)); + options.setStoreJson(Collections.singletonList(JsonFormat.printer().print(redis))); + options.setDefaultFeastProject("myproject"); options.setProject(""); options.setBlockOnRun(false); @@ -178,9 +187,26 @@ public void runPipeline_ShouldWriteToRedisCorrectlyGivenValidSpecAndFeatureRow() IntStream.range(0, IMPORT_JOB_SAMPLE_FEATURE_ROW_SIZE) .forEach( i -> { - FeatureRow randomRow = TestUtil.createRandomFeatureRow(featureSet); - RedisKey redisKey = TestUtil.createRedisKey(featureSet, randomRow); + FeatureRow randomRow = TestUtil.createRandomFeatureRow(featureSet.getSpec()); + RedisKey redisKey = TestUtil.createRedisKey(featureSet.getSpec(), randomRow); input.add(randomRow); + List fields = + randomRow.getFieldsList().stream() + .filter( + field -> + spec.getFeaturesList().stream() + .map(FeatureSpec::getName) + .collect(Collectors.toList()) + .contains(field.getName())) + .map(field -> field.toBuilder().clearName().build()) + .collect(Collectors.toList()); + randomRow = + randomRow + .toBuilder() + .clearFields() + .addAllFields(fields) + .clearFeatureSet() + .build(); expected.put(redisKey, randomRow); }); @@ -202,21 +228,24 @@ public void runPipeline_ShouldWriteToRedisCorrectlyGivenValidSpecAndFeatureRow() Duration.standardSeconds(IMPORT_JOB_CHECK_INTERVAL_DURATION_SEC)); LOGGER.info("Validating the actual values written to Redis ..."); - Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT); + RedisClient redisClient = + RedisClient.create(new RedisURI(REDIS_HOST, REDIS_PORT, java.time.Duration.ofMillis(2000))); + StatefulRedisConnection connection = redisClient.connect(new ByteArrayCodec()); + RedisCommands sync = connection.sync(); expected.forEach( (key, expectedValue) -> { // Ensure ingested key exists. - byte[] actualByteValue = jedis.get(key.toByteArray()); + byte[] actualByteValue = sync.get(key.toByteArray()); if (actualByteValue == null) { LOGGER.error("Key not found in Redis: " + key); LOGGER.info("Redis INFO:"); - LOGGER.info(jedis.info()); - String randomKey = jedis.randomKey(); + LOGGER.info(sync.info()); + byte[] randomKey = sync.randomkey(); if (randomKey != null) { LOGGER.info("Sample random key, value (for debugging purpose):"); LOGGER.info("Key: " + randomKey); - LOGGER.info("Value: " + jedis.get(randomKey)); + LOGGER.info("Value: " + sync.get(randomKey)); } Assert.fail("Missing key in Redis."); } @@ -235,5 +264,6 @@ public void runPipeline_ShouldWriteToRedisCorrectlyGivenValidSpecAndFeatureRow() // Ensure the retrieved FeatureRow is equal to the ingested FeatureRow. Assert.assertEquals(expectedValue, actualValue); }); + redisClient.shutdown(); } } diff --git a/ingestion/src/test/java/feast/ingestion/options/BZip2CompressorTest.java b/ingestion/src/test/java/feast/ingestion/options/BZip2CompressorTest.java new file mode 100644 index 00000000000..cd03b18c793 --- /dev/null +++ b/ingestion/src/test/java/feast/ingestion/options/BZip2CompressorTest.java @@ -0,0 +1,40 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.ingestion.options; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; +import org.junit.Assert; +import org.junit.Test; + +public class BZip2CompressorTest { + + @Test + public void shouldHavBZip2CompatibleOutput() throws IOException { + BZip2Compressor compressor = new BZip2Compressor<>(String::getBytes); + String origString = "somestring"; + try (ByteArrayInputStream inputStream = + new ByteArrayInputStream(compressor.compress(origString)); + BZip2CompressorInputStream bzip2Input = new BZip2CompressorInputStream(inputStream); + BufferedReader reader = new BufferedReader(new InputStreamReader(bzip2Input))) { + Assert.assertEquals(origString, reader.readLine()); + } + } +} diff --git a/ingestion/src/test/java/feast/ingestion/options/BZip2DecompressorTest.java b/ingestion/src/test/java/feast/ingestion/options/BZip2DecompressorTest.java new file mode 100644 index 00000000000..fe7cc789d86 --- /dev/null +++ b/ingestion/src/test/java/feast/ingestion/options/BZip2DecompressorTest.java @@ -0,0 +1,48 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.ingestion.options; + +import static org.junit.Assert.*; + +import java.io.*; +import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream; +import org.junit.Test; + +public class BZip2DecompressorTest { + + @Test + public void shouldDecompressBZip2Stream() throws IOException { + BZip2Decompressor decompressor = + new BZip2Decompressor<>( + inputStream -> { + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + String output = reader.readLine(); + reader.close(); + return output; + }); + + String originalString = "abc"; + ByteArrayOutputStream compressedStream = new ByteArrayOutputStream(); + try (BZip2CompressorOutputStream bzip2Output = + new BZip2CompressorOutputStream(compressedStream)) { + bzip2Output.write(originalString.getBytes()); + } + + String decompressedString = decompressor.decompress(compressedStream.toByteArray()); + assertEquals(originalString, decompressedString); + } +} diff --git a/ingestion/src/test/java/feast/ingestion/options/StringListStreamConverterTest.java b/ingestion/src/test/java/feast/ingestion/options/StringListStreamConverterTest.java new file mode 100644 index 00000000000..5ce9f054bc9 --- /dev/null +++ b/ingestion/src/test/java/feast/ingestion/options/StringListStreamConverterTest.java @@ -0,0 +1,36 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.ingestion.options; + +import static org.junit.Assert.*; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import org.junit.Test; + +public class StringListStreamConverterTest { + + @Test + public void shouldReadStreamAsNewlineSeparatedStrings() throws IOException { + StringListStreamConverter converter = new StringListStreamConverter(); + String originalString = "abc\ndef"; + InputStream stringStream = new ByteArrayInputStream(originalString.getBytes()); + assertEquals(Arrays.asList("abc", "def"), converter.readStream(stringStream)); + } +} diff --git a/ingestion/src/test/java/feast/ingestion/transform/ProcessAndValidateFeatureRowsTest.java b/ingestion/src/test/java/feast/ingestion/transform/ProcessAndValidateFeatureRowsTest.java new file mode 100644 index 00000000000..8c5d7bd8ed6 --- /dev/null +++ b/ingestion/src/test/java/feast/ingestion/transform/ProcessAndValidateFeatureRowsTest.java @@ -0,0 +1,276 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2019 The Feast Authors + * + * 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 feast.ingestion.transform; + +import feast.proto.core.FeatureSetProto.EntitySpec; +import feast.proto.core.FeatureSetProto.FeatureSetSpec; +import feast.proto.core.FeatureSetProto.FeatureSpec; +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.proto.types.FieldProto.Field; +import feast.proto.types.ValueProto.Value; +import feast.proto.types.ValueProto.ValueType.Enum; +import feast.storage.api.writer.FailedElement; +import feast.test.TestUtil; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.beam.sdk.extensions.protobuf.ProtoCoder; +import org.apache.beam.sdk.testing.PAssert; +import org.apache.beam.sdk.testing.TestPipeline; +import org.apache.beam.sdk.transforms.Count; +import org.apache.beam.sdk.transforms.Create; +import org.apache.beam.sdk.values.PCollectionTuple; +import org.apache.beam.sdk.values.TupleTag; +import org.junit.Rule; +import org.junit.Test; + +public class ProcessAndValidateFeatureRowsTest { + + @Rule public transient TestPipeline p = TestPipeline.create(); + + private static final TupleTag SUCCESS_TAG = new TupleTag() {}; + + private static final TupleTag FAILURE_TAG = new TupleTag() {}; + + @Test + public void shouldWriteSuccessAndFailureTagsCorrectly() { + FeatureSetSpec fs1 = + FeatureSetSpec.newBuilder() + .setName("feature_set") + .setProject("myproject") + .addEntities( + EntitySpec.newBuilder() + .setName("entity_id_primary") + .setValueType(Enum.INT32) + .build()) + .addEntities( + EntitySpec.newBuilder() + .setName("entity_id_secondary") + .setValueType(Enum.STRING) + .build()) + .addFeatures( + FeatureSpec.newBuilder().setName("feature_1").setValueType(Enum.STRING).build()) + .addFeatures( + FeatureSpec.newBuilder().setName("feature_2").setValueType(Enum.INT64).build()) + .build(); + + FeatureSetSpec fs2 = + FeatureSetSpec.newBuilder() + .setName("feature_set_2") + .setProject("myproject") + .addEntities( + EntitySpec.newBuilder() + .setName("entity_id_primary") + .setValueType(Enum.INT32) + .build()) + .addEntities( + EntitySpec.newBuilder() + .setName("entity_id_secondary") + .setValueType(Enum.STRING) + .build()) + .addFeatures( + FeatureSpec.newBuilder().setName("feature_1").setValueType(Enum.STRING).build()) + .addFeatures( + FeatureSpec.newBuilder().setName("feature_2").setValueType(Enum.INT64).build()) + .build(); + + Map featureSetSpecs = new HashMap<>(); + featureSetSpecs.put("myproject/feature_set", fs1); + featureSetSpecs.put("myproject/feature_set_2", fs2); + + List input = new ArrayList<>(); + List expected = new ArrayList<>(); + + for (FeatureSetSpec featureSetSpec : featureSetSpecs.values()) { + FeatureRow randomRow = TestUtil.createRandomFeatureRow(featureSetSpec); + input.add(randomRow); + expected.add(randomRow); + } + + input.add(FeatureRow.newBuilder().setFeatureSet("invalid").build()); + + PCollectionTuple output = + p.apply(Create.of(input)) + .setCoder(ProtoCoder.of(FeatureRow.class)) + .apply( + ProcessAndValidateFeatureRows.newBuilder() + .setDefaultProject("myproject") + .setFailureTag(FAILURE_TAG) + .setSuccessTag(SUCCESS_TAG) + .setFeatureSetSpecs(featureSetSpecs) + .build()); + + PAssert.that(output.get(SUCCESS_TAG)).containsInAnyOrder(expected); + PAssert.that(output.get(FAILURE_TAG).apply(Count.globally())).containsInAnyOrder(1L); + + p.run(); + } + + @Test + public void shouldStripVersions() { + FeatureSetSpec fs1 = + FeatureSetSpec.newBuilder() + .setName("feature_set") + .setProject("myproject") + .addEntities( + EntitySpec.newBuilder() + .setName("entity_id_primary") + .setValueType(Enum.INT32) + .build()) + .addEntities( + EntitySpec.newBuilder() + .setName("entity_id_secondary") + .setValueType(Enum.STRING) + .build()) + .addFeatures( + FeatureSpec.newBuilder().setName("feature_1").setValueType(Enum.STRING).build()) + .addFeatures( + FeatureSpec.newBuilder().setName("feature_2").setValueType(Enum.INT64).build()) + .build(); + + Map featureSetSpecs = new HashMap<>(); + featureSetSpecs.put("myproject/feature_set", fs1); + + List input = new ArrayList<>(); + List expected = new ArrayList<>(); + + FeatureRow randomRow = TestUtil.createRandomFeatureRow(fs1); + expected.add(randomRow); + randomRow = randomRow.toBuilder().setFeatureSet("myproject/feature_set:1").build(); + input.add(randomRow); + + PCollectionTuple output = + p.apply(Create.of(input)) + .setCoder(ProtoCoder.of(FeatureRow.class)) + .apply( + ProcessAndValidateFeatureRows.newBuilder() + .setDefaultProject("myproject") + .setFailureTag(FAILURE_TAG) + .setSuccessTag(SUCCESS_TAG) + .setFeatureSetSpecs(featureSetSpecs) + .build()); + + PAssert.that(output.get(SUCCESS_TAG)).containsInAnyOrder(expected); + + p.run(); + } + + @Test + public void shouldApplyDefaultProject() { + FeatureSetSpec fs1 = + FeatureSetSpec.newBuilder() + .setName("feature_set") + .setProject("myproject") + .addEntities( + EntitySpec.newBuilder() + .setName("entity_id_primary") + .setValueType(Enum.INT32) + .build()) + .addEntities( + EntitySpec.newBuilder() + .setName("entity_id_secondary") + .setValueType(Enum.STRING) + .build()) + .addFeatures( + FeatureSpec.newBuilder().setName("feature_1").setValueType(Enum.STRING).build()) + .addFeatures( + FeatureSpec.newBuilder().setName("feature_2").setValueType(Enum.INT64).build()) + .build(); + + Map featureSetSpecs = new HashMap<>(); + featureSetSpecs.put("myproject/feature_set", fs1); + + List input = new ArrayList<>(); + List expected = new ArrayList<>(); + + FeatureRow randomRow = TestUtil.createRandomFeatureRow(fs1); + expected.add(randomRow); + randomRow = randomRow.toBuilder().setFeatureSet("feature_set").build(); + input.add(randomRow); + + PCollectionTuple output = + p.apply(Create.of(input)) + .setCoder(ProtoCoder.of(FeatureRow.class)) + .apply( + ProcessAndValidateFeatureRows.newBuilder() + .setDefaultProject("myproject") + .setFailureTag(FAILURE_TAG) + .setSuccessTag(SUCCESS_TAG) + .setFeatureSetSpecs(featureSetSpecs) + .build()); + + PAssert.that(output.get(SUCCESS_TAG)).containsInAnyOrder(expected); + + p.run(); + } + + @Test + public void shouldExcludeUnregisteredFields() { + FeatureSetSpec fs1 = + FeatureSetSpec.newBuilder() + .setName("feature_set") + .setProject("myproject") + .addEntities( + EntitySpec.newBuilder() + .setName("entity_id_primary") + .setValueType(Enum.INT32) + .build()) + .addEntities( + EntitySpec.newBuilder() + .setName("entity_id_secondary") + .setValueType(Enum.STRING) + .build()) + .addFeatures( + FeatureSpec.newBuilder().setName("feature_1").setValueType(Enum.STRING).build()) + .addFeatures( + FeatureSpec.newBuilder().setName("feature_2").setValueType(Enum.INT64).build()) + .build(); + + Map featureSets = new HashMap<>(); + featureSets.put("myproject/feature_set", fs1); + + List input = new ArrayList<>(); + List expected = new ArrayList<>(); + + FeatureRow randomRow = TestUtil.createRandomFeatureRow(fs1); + expected.add(randomRow); + input.add( + randomRow + .toBuilder() + .addFields( + Field.newBuilder() + .setName("extra") + .setValue(Value.newBuilder().setStringVal("hello"))) + .build()); + + PCollectionTuple output = + p.apply(Create.of(input)) + .setCoder(ProtoCoder.of(FeatureRow.class)) + .apply( + ProcessAndValidateFeatureRows.newBuilder() + .setDefaultProject("myproject") + .setFailureTag(FAILURE_TAG) + .setSuccessTag(SUCCESS_TAG) + .setFeatureSetSpecs(featureSets) + .build()); + + PAssert.that(output.get(SUCCESS_TAG)).containsInAnyOrder(expected); + + p.run(); + } +} diff --git a/ingestion/src/test/java/feast/ingestion/transform/ValidateFeatureRowsTest.java b/ingestion/src/test/java/feast/ingestion/transform/ValidateFeatureRowsTest.java deleted file mode 100644 index aca39563877..00000000000 --- a/ingestion/src/test/java/feast/ingestion/transform/ValidateFeatureRowsTest.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright 2018-2019 The Feast Authors - * - * 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 feast.ingestion.transform; - -import static org.junit.Assert.*; - -import feast.core.FeatureSetProto.EntitySpec; -import feast.core.FeatureSetProto.FeatureSet; -import feast.core.FeatureSetProto.FeatureSetSpec; -import feast.core.FeatureSetProto.FeatureSpec; -import feast.ingestion.values.FailedElement; -import feast.test.TestUtil; -import feast.types.FeatureRowProto.FeatureRow; -import feast.types.FieldProto.Field; -import feast.types.ValueProto.Value; -import feast.types.ValueProto.ValueType.Enum; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.apache.beam.sdk.extensions.protobuf.ProtoCoder; -import org.apache.beam.sdk.testing.PAssert; -import org.apache.beam.sdk.testing.TestPipeline; -import org.apache.beam.sdk.transforms.Count; -import org.apache.beam.sdk.transforms.Create; -import org.apache.beam.sdk.values.PCollectionTuple; -import org.apache.beam.sdk.values.TupleTag; -import org.junit.Rule; -import org.junit.Test; - -public class ValidateFeatureRowsTest { - - @Rule public transient TestPipeline p = TestPipeline.create(); - - private static final TupleTag SUCCESS_TAG = new TupleTag() {}; - - private static final TupleTag FAILURE_TAG = new TupleTag() {}; - - @Test - public void shouldWriteSuccessAndFailureTagsCorrectly() { - FeatureSet fs1 = - FeatureSet.newBuilder() - .setSpec( - FeatureSetSpec.newBuilder() - .setName("feature_set") - .setVersion(1) - .setProject("myproject") - .addEntities( - EntitySpec.newBuilder() - .setName("entity_id_primary") - .setValueType(Enum.INT32) - .build()) - .addEntities( - EntitySpec.newBuilder() - .setName("entity_id_secondary") - .setValueType(Enum.STRING) - .build()) - .addFeatures( - FeatureSpec.newBuilder() - .setName("feature_1") - .setValueType(Enum.STRING) - .build()) - .addFeatures( - FeatureSpec.newBuilder() - .setName("feature_2") - .setValueType(Enum.INT64) - .build())) - .build(); - - FeatureSet fs2 = - FeatureSet.newBuilder() - .setSpec( - FeatureSetSpec.newBuilder() - .setName("feature_set") - .setVersion(2) - .setProject("myproject") - .addEntities( - EntitySpec.newBuilder() - .setName("entity_id_primary") - .setValueType(Enum.INT32) - .build()) - .addEntities( - EntitySpec.newBuilder() - .setName("entity_id_secondary") - .setValueType(Enum.STRING) - .build()) - .addFeatures( - FeatureSpec.newBuilder() - .setName("feature_1") - .setValueType(Enum.STRING) - .build()) - .addFeatures( - FeatureSpec.newBuilder() - .setName("feature_2") - .setValueType(Enum.INT64) - .build())) - .build(); - - Map featureSets = new HashMap<>(); - featureSets.put("myproject/feature_set:1", fs1); - featureSets.put("myproject/feature_set:2", fs2); - - List input = new ArrayList<>(); - List expected = new ArrayList<>(); - - for (FeatureSet featureSet : featureSets.values()) { - FeatureRow randomRow = TestUtil.createRandomFeatureRow(featureSet); - input.add(randomRow); - expected.add(randomRow); - } - - input.add(FeatureRow.newBuilder().setFeatureSet("invalid").build()); - - PCollectionTuple output = - p.apply(Create.of(input)) - .setCoder(ProtoCoder.of(FeatureRow.class)) - .apply( - ValidateFeatureRows.newBuilder() - .setFailureTag(FAILURE_TAG) - .setSuccessTag(SUCCESS_TAG) - .setFeatureSets(featureSets) - .build()); - - PAssert.that(output.get(SUCCESS_TAG)).containsInAnyOrder(expected); - PAssert.that(output.get(FAILURE_TAG).apply(Count.globally())).containsInAnyOrder(1L); - - p.run(); - } - - @Test - public void shouldExcludeUnregisteredFields() { - FeatureSet fs1 = - FeatureSet.newBuilder() - .setSpec( - FeatureSetSpec.newBuilder() - .setName("feature_set") - .setVersion(1) - .setProject("myproject") - .addEntities( - EntitySpec.newBuilder() - .setName("entity_id_primary") - .setValueType(Enum.INT32) - .build()) - .addEntities( - EntitySpec.newBuilder() - .setName("entity_id_secondary") - .setValueType(Enum.STRING) - .build()) - .addFeatures( - FeatureSpec.newBuilder() - .setName("feature_1") - .setValueType(Enum.STRING) - .build()) - .addFeatures( - FeatureSpec.newBuilder() - .setName("feature_2") - .setValueType(Enum.INT64) - .build())) - .build(); - - Map featureSets = new HashMap<>(); - featureSets.put("myproject/feature_set:1", fs1); - - List input = new ArrayList<>(); - List expected = new ArrayList<>(); - - FeatureRow randomRow = TestUtil.createRandomFeatureRow(fs1); - expected.add(randomRow); - input.add(randomRow.toBuilder() - .addFields(Field.newBuilder() - .setName("extra") - .setValue(Value.newBuilder().setStringVal("hello"))) - .build() - ); - - PCollectionTuple output = - p.apply(Create.of(input)) - .setCoder(ProtoCoder.of(FeatureRow.class)) - .apply( - ValidateFeatureRows.newBuilder() - .setFailureTag(FAILURE_TAG) - .setSuccessTag(SUCCESS_TAG) - .setFeatureSets(featureSets) - .build()); - - PAssert.that(output.get(SUCCESS_TAG)).containsInAnyOrder(expected); - - p.run(); - } -} diff --git a/ingestion/src/test/java/feast/ingestion/transform/metrics/WriteFeatureValueMetricsDoFnTest.java b/ingestion/src/test/java/feast/ingestion/transform/metrics/WriteFeatureValueMetricsDoFnTest.java new file mode 100644 index 00000000000..e4686e89aaa --- /dev/null +++ b/ingestion/src/test/java/feast/ingestion/transform/metrics/WriteFeatureValueMetricsDoFnTest.java @@ -0,0 +1,282 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.ingestion.transform.metrics; + +import static org.junit.Assert.fail; + +import com.google.protobuf.ByteString; +import com.google.protobuf.Timestamp; +import com.google.protobuf.util.Timestamps; +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.proto.types.FeatureRowProto.FeatureRow.Builder; +import feast.proto.types.FieldProto.Field; +import feast.proto.types.ValueProto.BoolList; +import feast.proto.types.ValueProto.BytesList; +import feast.proto.types.ValueProto.DoubleList; +import feast.proto.types.ValueProto.FloatList; +import feast.proto.types.ValueProto.Int32List; +import feast.proto.types.ValueProto.Int64List; +import feast.proto.types.ValueProto.StringList; +import feast.proto.types.ValueProto.Value; +import feast.test.TestUtil.DummyStatsDServer; +import java.io.BufferedReader; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import org.apache.beam.sdk.options.PipelineOptions; +import org.apache.beam.sdk.options.PipelineOptionsFactory; +import org.apache.beam.sdk.testing.TestPipeline; +import org.apache.beam.sdk.transforms.Create; +import org.apache.beam.sdk.transforms.ParDo; +import org.junit.Rule; +import org.junit.Test; + +public class WriteFeatureValueMetricsDoFnTest { + + @Rule public final transient TestPipeline pipeline = TestPipeline.create(); + private static final int STATSD_SERVER_PORT = 17254; + private final DummyStatsDServer statsDServer = new DummyStatsDServer(STATSD_SERVER_PORT); + + @Test + public void shouldSendCorrectStatsDMetrics() throws IOException, InterruptedException { + PipelineOptions pipelineOptions = PipelineOptionsFactory.create(); + pipelineOptions.setJobName("job"); + + Map> input = + readTestInput("feast/ingestion/transform/WriteFeatureValueMetricsDoFnTest.input"); + List expectedLines = + readTestOutput("feast/ingestion/transform/WriteFeatureValueMetricsDoFnTest.output"); + + pipeline + .apply(Create.of(input)) + .apply( + ParDo.of( + WriteFeatureValueMetricsDoFn.newBuilder() + .setStatsdHost("localhost") + .setStatsdPort(STATSD_SERVER_PORT) + .setStoreName("store") + .build())); + pipeline.run(pipelineOptions).waitUntilFinish(); + // Wait until StatsD has finished processed all messages, 3 sec is a reasonable duration + // based on empirical testing. + Thread.sleep(3000); + + List actualLines = statsDServer.messagesReceived(); + for (String expected : expectedLines) { + boolean matched = false; + for (String actual : actualLines) { + if (actual.equals(expected)) { + matched = true; + break; + } + } + if (!matched) { + System.out.println("Print actual metrics output for debugging:"); + for (String line : actualLines) { + System.out.println(line); + } + fail(String.format("Expected StatsD metric not found:\n%s", expected)); + } + } + statsDServer.stop(); + } + + // Test utility method to read expected StatsD metrics output from a text file. + @SuppressWarnings("SameParameterValue") + public static List readTestOutput(String path) throws IOException { + URL url = Thread.currentThread().getContextClassLoader().getResource(path); + if (url == null) { + throw new IllegalArgumentException( + "cannot read test data, path contains null url. Path: " + path); + } + List lines = new ArrayList<>(); + try (BufferedReader reader = Files.newBufferedReader(Paths.get(url.getPath()))) { + String line = reader.readLine(); + while (line != null) { + if (line.trim().length() > 1) { + lines.add(line); + } + line = reader.readLine(); + } + } + return lines; + } + + public static Map> readTestInput(String path) throws IOException { + return readTestInput(path, null); + } + + // Test utility method to create test feature row data from a text file. + // If tsOverride is not null, all the feature row will have the same timestamp "tsOverride". + // Else if there exist a "timestamp" column with RFC3339 format, the feature row will be assigned + // that timestamp. + // Else no timestamp will be assigned (the feature row will have the default proto Timestamp + // object). + @SuppressWarnings("SameParameterValue") + public static Map> readTestInput(String path, Timestamp tsOverride) + throws IOException { + Map> data = new HashMap<>(); + URL url = Thread.currentThread().getContextClassLoader().getResource(path); + if (url == null) { + throw new IllegalArgumentException( + "cannot read test data, path contains null url. Path: " + path); + } + List lines = new ArrayList<>(); + try (BufferedReader reader = Files.newBufferedReader(Paths.get(url.getPath()))) { + String line = reader.readLine(); + while (line != null) { + lines.add(line); + line = reader.readLine(); + } + } + List colNames = new ArrayList<>(); + for (String line : lines) { + if (line.trim().length() < 1) { + continue; + } + String[] splits = line.split(","); + colNames.addAll(Arrays.asList(splits)); + + if (line.startsWith("featuresetref")) { + // Header line + colNames.addAll(Arrays.asList(splits).subList(1, splits.length)); + continue; + } + + Builder featureRowBuilder = FeatureRow.newBuilder(); + for (int i = 0; i < splits.length; i++) { + String colVal = splits[i].trim(); + if (i == 0) { + featureRowBuilder.setFeatureSet(colVal); + continue; + } + String colName = colNames.get(i); + if (colName.equals("timestamp")) { + Instant instant = Instant.parse(colVal); + featureRowBuilder.setEventTimestamp( + Timestamps.fromNanos(instant.getEpochSecond() * 1_000_000_000 + instant.getNano())); + continue; + } + + Field.Builder fieldBuilder = Field.newBuilder().setName(colName); + if (!colVal.isEmpty()) { + switch (colName) { + case "int32": + fieldBuilder.setValue(Value.newBuilder().setInt32Val((Integer.parseInt(colVal)))); + break; + case "int64": + fieldBuilder.setValue(Value.newBuilder().setInt64Val((Long.parseLong(colVal)))); + break; + case "double": + fieldBuilder.setValue(Value.newBuilder().setDoubleVal((Double.parseDouble(colVal)))); + break; + case "float": + fieldBuilder.setValue(Value.newBuilder().setFloatVal((Float.parseFloat(colVal)))); + break; + case "bool": + fieldBuilder.setValue(Value.newBuilder().setBoolVal((Boolean.parseBoolean(colVal)))); + break; + case "int32list": + List int32List = new ArrayList<>(); + for (String val : colVal.split("\\|")) { + int32List.add(Integer.parseInt(val)); + } + fieldBuilder.setValue( + Value.newBuilder().setInt32ListVal(Int32List.newBuilder().addAllVal(int32List))); + break; + case "int64list": + List int64list = new ArrayList<>(); + for (String val : colVal.split("\\|")) { + int64list.add(Long.parseLong(val)); + } + fieldBuilder.setValue( + Value.newBuilder().setInt64ListVal(Int64List.newBuilder().addAllVal(int64list))); + break; + case "doublelist": + List doubleList = new ArrayList<>(); + for (String val : colVal.split("\\|")) { + doubleList.add(Double.parseDouble(val)); + } + fieldBuilder.setValue( + Value.newBuilder() + .setDoubleListVal(DoubleList.newBuilder().addAllVal(doubleList))); + break; + case "floatlist": + List floatList = new ArrayList<>(); + for (String val : colVal.split("\\|")) { + floatList.add(Float.parseFloat(val)); + } + fieldBuilder.setValue( + Value.newBuilder().setFloatListVal(FloatList.newBuilder().addAllVal(floatList))); + break; + case "boollist": + List boolList = new ArrayList<>(); + for (String val : colVal.split("\\|")) { + boolList.add(Boolean.parseBoolean(val)); + } + fieldBuilder.setValue( + Value.newBuilder().setBoolListVal(BoolList.newBuilder().addAllVal(boolList))); + break; + case "bytes": + fieldBuilder.setValue( + Value.newBuilder().setBytesVal(ByteString.copyFromUtf8("Dummy"))); + break; + case "byteslist": + fieldBuilder.setValue( + Value.newBuilder().setBytesListVal(BytesList.getDefaultInstance())); + break; + case "string": + fieldBuilder.setValue(Value.newBuilder().setStringVal("Dummy")); + break; + case "stringlist": + fieldBuilder.setValue( + Value.newBuilder().setStringListVal(StringList.getDefaultInstance())); + break; + } + } + featureRowBuilder.addFields(fieldBuilder); + } + + if (!data.containsKey(featureRowBuilder.getFeatureSet())) { + data.put(featureRowBuilder.getFeatureSet(), new ArrayList<>()); + } + List featureRowsByFeatureSetRef = data.get(featureRowBuilder.getFeatureSet()); + if (tsOverride != null) { + featureRowBuilder.setEventTimestamp(tsOverride); + } + featureRowsByFeatureSetRef.add(featureRowBuilder.build()); + } + + // Convert List to Iterable to match the function signature in + // WriteFeatureValueMetricsDoFn + Map> dataWithIterable = new HashMap<>(); + for (Entry> entrySet : data.entrySet()) { + String key = entrySet.getKey(); + Iterable value = entrySet.getValue(); + dataWithIterable.put(key, value); + } + return dataWithIterable; + } +} diff --git a/ingestion/src/test/java/feast/ingestion/transform/metrics/WriteRowMetricsDoFnTest.java b/ingestion/src/test/java/feast/ingestion/transform/metrics/WriteRowMetricsDoFnTest.java new file mode 100644 index 00000000000..3309a0cdebc --- /dev/null +++ b/ingestion/src/test/java/feast/ingestion/transform/metrics/WriteRowMetricsDoFnTest.java @@ -0,0 +1,88 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.ingestion.transform.metrics; + +import static feast.ingestion.transform.metrics.WriteFeatureValueMetricsDoFnTest.readTestInput; +import static feast.ingestion.transform.metrics.WriteFeatureValueMetricsDoFnTest.readTestOutput; +import static org.junit.Assert.fail; + +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.test.TestUtil.DummyStatsDServer; +import java.io.IOException; +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; +import java.util.List; +import java.util.Map; +import org.apache.beam.sdk.options.PipelineOptions; +import org.apache.beam.sdk.options.PipelineOptionsFactory; +import org.apache.beam.sdk.testing.TestPipeline; +import org.apache.beam.sdk.transforms.Create; +import org.apache.beam.sdk.transforms.ParDo; +import org.junit.Rule; +import org.junit.Test; + +public class WriteRowMetricsDoFnTest { + + @Rule public final transient TestPipeline pipeline = TestPipeline.create(); + private static final int STATSD_SERVER_PORT = 17255; + private final DummyStatsDServer statsDServer = new DummyStatsDServer(STATSD_SERVER_PORT); + + @Test + public void shouldSendCorrectStatsDMetrics() throws IOException, InterruptedException { + PipelineOptions pipelineOptions = PipelineOptionsFactory.create(); + pipelineOptions.setJobName("job"); + Map> input = + readTestInput("feast/ingestion/transform/WriteRowMetricsDoFnTest.input"); + List expectedLines = + readTestOutput("feast/ingestion/transform/WriteRowMetricsDoFnTest.output"); + + pipeline + .apply(Create.of(input)) + .apply( + ParDo.of( + WriteRowMetricsDoFn.newBuilder() + .setStatsdHost("localhost") + .setStatsdPort(STATSD_SERVER_PORT) + .setStoreName("store") + .setClock(Clock.fixed(Instant.ofEpochSecond(1585548645), ZoneId.of("UTC"))) + .build())); + pipeline.run(pipelineOptions).waitUntilFinish(); + // Wait until StatsD has finished processed all messages, 3 sec is a reasonable duration + // based on empirical testing. + Thread.sleep(3000); + + List actualLines = statsDServer.messagesReceived(); + for (String expected : expectedLines) { + boolean matched = false; + for (String actual : actualLines) { + if (actual.equals(expected)) { + matched = true; + break; + } + } + if (!matched) { + System.out.println("Print actual metrics output for debugging:"); + for (String line : actualLines) { + System.out.println(line); + } + fail(String.format("Expected StatsD metric not found:\n%s", expected)); + } + } + statsDServer.stop(); + } +} diff --git a/ingestion/src/test/java/feast/ingestion/util/StoreUtilTest.java b/ingestion/src/test/java/feast/ingestion/util/StoreUtilTest.java deleted file mode 100644 index 4e2297e405d..00000000000 --- a/ingestion/src/test/java/feast/ingestion/util/StoreUtilTest.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright 2018-2019 The Feast Authors - * - * 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 feast.ingestion.util; - -import static feast.types.ValueProto.ValueType.Enum.BOOL; -import static feast.types.ValueProto.ValueType.Enum.BOOL_LIST; -import static feast.types.ValueProto.ValueType.Enum.BYTES; -import static feast.types.ValueProto.ValueType.Enum.BYTES_LIST; -import static feast.types.ValueProto.ValueType.Enum.DOUBLE; -import static feast.types.ValueProto.ValueType.Enum.DOUBLE_LIST; -import static feast.types.ValueProto.ValueType.Enum.FLOAT; -import static feast.types.ValueProto.ValueType.Enum.FLOAT_LIST; -import static feast.types.ValueProto.ValueType.Enum.INT32; -import static feast.types.ValueProto.ValueType.Enum.INT32_LIST; -import static feast.types.ValueProto.ValueType.Enum.INT64; -import static feast.types.ValueProto.ValueType.Enum.INT64_LIST; -import static feast.types.ValueProto.ValueType.Enum.STRING; -import static feast.types.ValueProto.ValueType.Enum.STRING_LIST; - -import com.google.cloud.bigquery.BigQuery; -import com.google.cloud.bigquery.Field; -import com.google.cloud.bigquery.Field.Mode; -import com.google.cloud.bigquery.Schema; -import com.google.cloud.bigquery.StandardSQLTypeName; -import feast.core.FeatureSetProto.EntitySpec; -import feast.core.FeatureSetProto.FeatureSet; -import feast.core.FeatureSetProto.FeatureSetSpec; -import feast.core.FeatureSetProto.FeatureSpec; -import feast.ingestion.utils.StoreUtil; -import java.util.Arrays; -import org.junit.Assert; -import org.junit.Test; -import org.mockito.Mockito; - -public class StoreUtilTest { - - @Test - public void setupBigQuery_shouldCreateTable_givenValidFeatureSetSpec() { - FeatureSet featureSet = - FeatureSet.newBuilder() - .setSpec( - FeatureSetSpec.newBuilder() - .setName("feature_set_1") - .setVersion(1) - .setProject("feast-project") - .addEntities(EntitySpec.newBuilder().setName("entity_1").setValueType(INT32)) - .addFeatures(FeatureSpec.newBuilder().setName("feature_1").setValueType(INT32)) - .addFeatures( - FeatureSpec.newBuilder().setName("feature_2").setValueType(STRING_LIST))) - .build(); - BigQuery mockedBigquery = Mockito.mock(BigQuery.class); - StoreUtil.setupBigQuery(featureSet, "project-1", "dataset_1", mockedBigquery); - } - - @Test - public void createBigQueryTableDefinition_shouldCreateCorrectSchema_givenValidFeatureSetSpec() { - FeatureSetSpec input = - FeatureSetSpec.newBuilder() - .addAllEntities( - Arrays.asList( - EntitySpec.newBuilder().setName("bytes_entity").setValueType(BYTES).build(), - EntitySpec.newBuilder().setName("string_entity").setValueType(STRING).build(), - EntitySpec.newBuilder().setName("int32_entity").setValueType(INT32).build(), - EntitySpec.newBuilder().setName("int64_entity").setValueType(INT64).build(), - EntitySpec.newBuilder().setName("double_entity").setValueType(DOUBLE).build(), - EntitySpec.newBuilder().setName("float_entity").setValueType(FLOAT).build(), - EntitySpec.newBuilder().setName("bool_entity").setValueType(BOOL).build(), - EntitySpec.newBuilder() - .setName("bytes_list_entity") - .setValueType(BYTES_LIST) - .build(), - EntitySpec.newBuilder() - .setName("string_list_entity") - .setValueType(STRING_LIST) - .build(), - EntitySpec.newBuilder() - .setName("int32_list_entity") - .setValueType(INT32_LIST) - .build(), - EntitySpec.newBuilder() - .setName("int64_list_entity") - .setValueType(INT64_LIST) - .build(), - EntitySpec.newBuilder() - .setName("double_list_entity") - .setValueType(DOUBLE_LIST) - .build(), - EntitySpec.newBuilder() - .setName("float_list_entity") - .setValueType(FLOAT_LIST) - .build(), - EntitySpec.newBuilder() - .setName("bool_list_entity") - .setValueType(BOOL_LIST) - .build())) - .addAllFeatures( - Arrays.asList( - FeatureSpec.newBuilder().setName("bytes_feature").setValueType(BYTES).build(), - FeatureSpec.newBuilder().setName("string_feature").setValueType(STRING).build(), - FeatureSpec.newBuilder().setName("int32_feature").setValueType(INT32).build(), - FeatureSpec.newBuilder().setName("int64_feature").setValueType(INT64).build(), - FeatureSpec.newBuilder().setName("double_feature").setValueType(DOUBLE).build(), - FeatureSpec.newBuilder().setName("float_feature").setValueType(FLOAT).build(), - FeatureSpec.newBuilder().setName("bool_feature").setValueType(BOOL).build(), - FeatureSpec.newBuilder() - .setName("bytes_list_feature") - .setValueType(BYTES_LIST) - .build(), - FeatureSpec.newBuilder() - .setName("string_list_feature") - .setValueType(STRING_LIST) - .build(), - FeatureSpec.newBuilder() - .setName("int32_list_feature") - .setValueType(INT32_LIST) - .build(), - FeatureSpec.newBuilder() - .setName("int64_list_feature") - .setValueType(INT64_LIST) - .build(), - FeatureSpec.newBuilder() - .setName("double_list_feature") - .setValueType(DOUBLE_LIST) - .build(), - FeatureSpec.newBuilder() - .setName("float_list_feature") - .setValueType(FLOAT_LIST) - .build(), - FeatureSpec.newBuilder() - .setName("bool_list_feature") - .setValueType(BOOL_LIST) - .build())) - .build(); - - Schema actual = StoreUtil.createBigQueryTableDefinition(input).getSchema(); - - Schema expected = - Schema.of( - Arrays.asList( - // Fields from entity - Field.newBuilder("bytes_entity", StandardSQLTypeName.BYTES).build(), - Field.newBuilder("string_entity", StandardSQLTypeName.STRING).build(), - Field.newBuilder("int32_entity", StandardSQLTypeName.INT64).build(), - Field.newBuilder("int64_entity", StandardSQLTypeName.INT64).build(), - Field.newBuilder("double_entity", StandardSQLTypeName.FLOAT64).build(), - Field.newBuilder("float_entity", StandardSQLTypeName.FLOAT64).build(), - Field.newBuilder("bool_entity", StandardSQLTypeName.BOOL).build(), - Field.newBuilder("bytes_list_entity", StandardSQLTypeName.BYTES) - .setMode(Mode.REPEATED) - .build(), - Field.newBuilder("string_list_entity", StandardSQLTypeName.STRING) - .setMode(Mode.REPEATED) - .build(), - Field.newBuilder("int32_list_entity", StandardSQLTypeName.INT64) - .setMode(Mode.REPEATED) - .build(), - Field.newBuilder("int64_list_entity", StandardSQLTypeName.INT64) - .setMode(Mode.REPEATED) - .build(), - Field.newBuilder("double_list_entity", StandardSQLTypeName.FLOAT64) - .setMode(Mode.REPEATED) - .build(), - Field.newBuilder("float_list_entity", StandardSQLTypeName.FLOAT64) - .setMode(Mode.REPEATED) - .build(), - Field.newBuilder("bool_list_entity", StandardSQLTypeName.BOOL) - .setMode(Mode.REPEATED) - .build(), - // Fields from feature - Field.newBuilder("bytes_feature", StandardSQLTypeName.BYTES).build(), - Field.newBuilder("string_feature", StandardSQLTypeName.STRING).build(), - Field.newBuilder("int32_feature", StandardSQLTypeName.INT64).build(), - Field.newBuilder("int64_feature", StandardSQLTypeName.INT64).build(), - Field.newBuilder("double_feature", StandardSQLTypeName.FLOAT64).build(), - Field.newBuilder("float_feature", StandardSQLTypeName.FLOAT64).build(), - Field.newBuilder("bool_feature", StandardSQLTypeName.BOOL).build(), - Field.newBuilder("bytes_list_feature", StandardSQLTypeName.BYTES) - .setMode(Mode.REPEATED) - .build(), - Field.newBuilder("string_list_feature", StandardSQLTypeName.STRING) - .setMode(Mode.REPEATED) - .build(), - Field.newBuilder("int32_list_feature", StandardSQLTypeName.INT64) - .setMode(Mode.REPEATED) - .build(), - Field.newBuilder("int64_list_feature", StandardSQLTypeName.INT64) - .setMode(Mode.REPEATED) - .build(), - Field.newBuilder("double_list_feature", StandardSQLTypeName.FLOAT64) - .setMode(Mode.REPEATED) - .build(), - Field.newBuilder("float_list_feature", StandardSQLTypeName.FLOAT64) - .setMode(Mode.REPEATED) - .build(), - Field.newBuilder("bool_list_feature", StandardSQLTypeName.BOOL) - .setMode(Mode.REPEATED) - .build(), - // Reserved fields - Field.newBuilder("event_timestamp", StandardSQLTypeName.TIMESTAMP) - .setDescription(StoreUtil.BIGQUERY_EVENT_TIMESTAMP_FIELD_DESCRIPTION) - .build(), - Field.newBuilder("created_timestamp", StandardSQLTypeName.TIMESTAMP) - .setDescription(StoreUtil.BIGQUERY_CREATED_TIMESTAMP_FIELD_DESCRIPTION) - .build(), - Field.newBuilder("job_id", StandardSQLTypeName.STRING) - .setDescription(StoreUtil.BIGQUERY_JOB_ID_FIELD_DESCRIPTION) - .build())); - - Assert.assertEquals(expected, actual); - } -} diff --git a/ingestion/src/test/java/feast/ingestion/util/DateUtilTest.java b/ingestion/src/test/java/feast/ingestion/utils/DateUtilTest.java similarity index 92% rename from ingestion/src/test/java/feast/ingestion/util/DateUtilTest.java rename to ingestion/src/test/java/feast/ingestion/utils/DateUtilTest.java index 71d4e67beaa..151d501a596 100644 --- a/ingestion/src/test/java/feast/ingestion/util/DateUtilTest.java +++ b/ingestion/src/test/java/feast/ingestion/utils/DateUtilTest.java @@ -14,15 +14,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package feast.ingestion.util; +package feast.ingestion.utils; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.*; import com.google.protobuf.Timestamp; -import feast.ingestion.utils.DateUtil; import junit.framework.TestCase; import org.joda.time.DateTime; diff --git a/ingestion/src/test/java/feast/ingestion/util/JsonUtilTest.java b/ingestion/src/test/java/feast/ingestion/utils/JsonUtilTest.java similarity index 95% rename from ingestion/src/test/java/feast/ingestion/util/JsonUtilTest.java rename to ingestion/src/test/java/feast/ingestion/utils/JsonUtilTest.java index 02af4d819f9..62c74dfc345 100644 --- a/ingestion/src/test/java/feast/ingestion/util/JsonUtilTest.java +++ b/ingestion/src/test/java/feast/ingestion/utils/JsonUtilTest.java @@ -14,12 +14,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package feast.ingestion.util; +package feast.ingestion.utils; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; -import feast.ingestion.utils.JsonUtil; import java.util.Collections; import java.util.HashMap; import java.util.Map; diff --git a/ingestion/src/test/java/feast/store/serving/redis/RedisCustomIOTest.java b/ingestion/src/test/java/feast/store/serving/redis/RedisCustomIOTest.java deleted file mode 100644 index 94167059b43..00000000000 --- a/ingestion/src/test/java/feast/store/serving/redis/RedisCustomIOTest.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright 2018-2019 The Feast Authors - * - * 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 feast.store.serving.redis; - -import feast.core.StoreProto; -import feast.storage.RedisProto.RedisKey; -import feast.store.serving.redis.RedisCustomIO.Method; -import feast.store.serving.redis.RedisCustomIO.RedisMutation; -import feast.types.FeatureRowProto.FeatureRow; -import feast.types.ValueProto.ValueType.Enum; -import org.apache.beam.sdk.testing.PAssert; -import org.apache.beam.sdk.testing.TestPipeline; -import org.apache.beam.sdk.transforms.Count; -import org.apache.beam.sdk.transforms.Create; -import org.apache.beam.sdk.values.PCollection; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import redis.clients.jedis.Jedis; -import redis.embedded.Redis; -import redis.embedded.RedisServer; - -import java.io.IOException; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -import static feast.test.TestUtil.field; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.MatcherAssert.assertThat; - -public class RedisCustomIOTest { - @Rule - public transient TestPipeline p = TestPipeline.create(); - - private static String REDIS_HOST = "localhost"; - private static int REDIS_PORT = 51234; - private Redis redis; - private Jedis jedis; - - - @Before - public void setUp() throws IOException { - redis = new RedisServer(REDIS_PORT); - redis.start(); - jedis = new Jedis(REDIS_HOST, REDIS_PORT); - } - - @After - public void teardown() { - redis.stop(); - } - - @Test - public void shouldWriteToRedis() { - StoreProto.Store.RedisConfig redisConfig = StoreProto.Store.RedisConfig.newBuilder() - .setHost(REDIS_HOST) - .setPort(REDIS_PORT) - .build(); - HashMap kvs = new LinkedHashMap<>(); - kvs.put( - RedisKey.newBuilder() - .setFeatureSet("fs:1") - .addEntities(field("entity", 1, Enum.INT64)) - .build(), - FeatureRow.newBuilder() - .setFeatureSet("fs:1") - .addFields(field("entity", 1, Enum.INT64)) - .addFields(field("feature", "one", Enum.STRING)) - .build()); - kvs.put( - RedisKey.newBuilder() - .setFeatureSet("fs:1") - .addEntities(field("entity", 2, Enum.INT64)) - .build(), - FeatureRow.newBuilder() - .setFeatureSet("fs:1") - .addFields(field("entity", 2, Enum.INT64)) - .addFields(field("feature", "two", Enum.STRING)) - .build()); - - List featureRowWrites = - kvs.entrySet().stream() - .map( - kv -> - new RedisMutation( - Method.SET, - kv.getKey().toByteArray(), - kv.getValue().toByteArray(), - null, - null)) - .collect(Collectors.toList()); - - p.apply(Create.of(featureRowWrites)) - .apply(RedisCustomIO.write(redisConfig)); - p.run(); - - kvs.forEach( - (key, value) -> { - byte[] actual = jedis.get(key.toByteArray()); - assertThat(actual, equalTo(value.toByteArray())); - }); - } - - @Test(timeout = 10000) - public void shouldRetryFailConnection() throws InterruptedException { - StoreProto.Store.RedisConfig redisConfig = StoreProto.Store.RedisConfig.newBuilder() - .setHost(REDIS_HOST) - .setPort(REDIS_PORT) - .setMaxRetries(4) - .setInitialBackoffMs(2000) - .build(); - HashMap kvs = new LinkedHashMap<>(); - kvs.put(RedisKey.newBuilder().setFeatureSet("fs:1") - .addEntities(field("entity", 1, Enum.INT64)).build(), - FeatureRow.newBuilder().setFeatureSet("fs:1") - .addFields(field("entity", 1, Enum.INT64)) - .addFields(field("feature", "one", Enum.STRING)).build()); - - List featureRowWrites = kvs.entrySet().stream() - .map(kv -> new RedisMutation(Method.SET, kv.getKey().toByteArray(), - kv.getValue().toByteArray(), - null, null) - ) - .collect(Collectors.toList()); - - PCollection failedElementCount = p.apply(Create.of(featureRowWrites)) - .apply(RedisCustomIO.write(redisConfig)) - .apply(Count.globally()); - - redis.stop(); - final ScheduledThreadPoolExecutor redisRestartExecutor = new ScheduledThreadPoolExecutor(1); - ScheduledFuture scheduledRedisRestart = redisRestartExecutor.schedule(() -> { - redis.start(); - }, 3, TimeUnit.SECONDS); - - PAssert.that(failedElementCount).containsInAnyOrder(0L); - p.run(); - scheduledRedisRestart.cancel(true); - - kvs.forEach((key, value) -> { - byte[] actual = jedis.get(key.toByteArray()); - assertThat(actual, equalTo(value.toByteArray())); - }); - } - - @Test - public void shouldProduceFailedElementIfRetryExceeded() { - StoreProto.Store.RedisConfig redisConfig = StoreProto.Store.RedisConfig.newBuilder() - .setHost(REDIS_HOST) - .setPort(REDIS_PORT) - .build(); - HashMap kvs = new LinkedHashMap<>(); - kvs.put(RedisKey.newBuilder().setFeatureSet("fs:1") - .addEntities(field("entity", 1, Enum.INT64)).build(), - FeatureRow.newBuilder().setFeatureSet("fs:1") - .addFields(field("entity", 1, Enum.INT64)) - .addFields(field("feature", "one", Enum.STRING)).build()); - - List featureRowWrites = kvs.entrySet().stream() - .map(kv -> new RedisMutation(Method.SET, kv.getKey().toByteArray(), - kv.getValue().toByteArray(), - null, null) - ).collect(Collectors.toList()); - - PCollection failedElementCount = p.apply(Create.of(featureRowWrites)) - .apply(RedisCustomIO.write(redisConfig)) - .apply(Count.globally()); - - redis.stop(); - PAssert.that(failedElementCount).containsInAnyOrder(1L); - p.run(); - } -} diff --git a/ingestion/src/test/java/feast/test/TestUtil.java b/ingestion/src/test/java/feast/test/TestUtil.java index 5c16d7e9e31..3204d93bcda 100644 --- a/ingestion/src/test/java/feast/test/TestUtil.java +++ b/ingestion/src/test/java/feast/test/TestUtil.java @@ -20,22 +20,21 @@ import com.google.protobuf.ByteString; import com.google.protobuf.util.Timestamps; -import feast.core.FeatureSetProto.FeatureSet; -import feast.ingestion.transform.WriteToStore; -import feast.storage.RedisProto.RedisKey; -import feast.types.FeatureRowProto.FeatureRow; -import feast.types.FeatureRowProto.FeatureRow.Builder; -import feast.types.FieldProto.Field; -import feast.types.ValueProto.BoolList; -import feast.types.ValueProto.BytesList; -import feast.types.ValueProto.DoubleList; -import feast.types.ValueProto.FloatList; -import feast.types.ValueProto.Int32List; -import feast.types.ValueProto.Int64List; -import feast.types.ValueProto.StringList; -import feast.types.ValueProto.Value; -import feast.types.ValueProto.ValueType; +import feast.ingestion.transform.metrics.WriteSuccessMetricsTransform; +import feast.proto.core.FeatureSetProto.FeatureSet; +import feast.proto.core.FeatureSetProto.FeatureSetSpec; +import feast.proto.storage.RedisProto.RedisKey; +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.proto.types.FeatureRowProto.FeatureRow.Builder; +import feast.proto.types.FieldProto.Field; +import feast.proto.types.ValueProto.*; import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.SocketException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Properties; import java.util.concurrent.ExecutionException; @@ -168,12 +167,15 @@ public static void publishFeatureRowsToKafka( /** * Create a Feature Row with random value according to the FeatureSetSpec * - *

See {@link #createRandomFeatureRow(FeatureSet, int)} + *

See {@link #createRandomFeatureRow(FeatureSetSpec, int)} + * + * @param featureSetSpec {@link FeatureSetSpec} + * @return {@link FeatureRow} */ - public static FeatureRow createRandomFeatureRow(FeatureSet featureSet) { + public static FeatureRow createRandomFeatureRow(FeatureSetSpec featureSetSpec) { ThreadLocalRandom random = ThreadLocalRandom.current(); int randomStringSizeMaxSize = 12; - return createRandomFeatureRow(featureSet, random.nextInt(0, randomStringSizeMaxSize) + 4); + return createRandomFeatureRow(featureSetSpec, random.nextInt(0, randomStringSizeMaxSize) + 4); } /** @@ -182,18 +184,18 @@ public static FeatureRow createRandomFeatureRow(FeatureSet featureSet) { *

The Feature Row created contains fields according to the entities and features defined in * FeatureSet, matching the value type of the field, with randomized value for testing. * - * @param featureSet {@link FeatureSet} + * @param featureSetSpec {@link FeatureSetSpec} * @param randomStringSize number of characters for the generated random string * @return {@link FeatureRow} */ - public static FeatureRow createRandomFeatureRow(FeatureSet featureSet, int randomStringSize) { + public static FeatureRow createRandomFeatureRow( + FeatureSetSpec featureSetSpec, int randomStringSize) { Builder builder = FeatureRow.newBuilder() - .setFeatureSet(getFeatureSetReference(featureSet)) + .setFeatureSet(getFeatureSetReference(featureSetSpec)) .setEventTimestamp(Timestamps.fromMillis(System.currentTimeMillis())); - featureSet - .getSpec() + featureSetSpec .getEntitiesList() .forEach( field -> { @@ -204,8 +206,7 @@ public static FeatureRow createRandomFeatureRow(FeatureSet featureSet, int rando .build()); }); - featureSet - .getSpec() + featureSetSpec .getFeaturesList() .forEach( field -> { @@ -295,15 +296,14 @@ public static Value createRandomValue(ValueType.Enum type, int randomStringSize) *

The entities in the created {@link RedisKey} will contain the value with matching field name * in the {@link FeatureRow} * - * @param featureSet {@link FeatureSet} + * @param featureSetSpec {@link FeatureSetSpec} * @param row {@link FeatureSet} * @return {@link RedisKey} */ - public static RedisKey createRedisKey(FeatureSet featureSet, FeatureRow row) { + public static RedisKey createRedisKey(FeatureSetSpec featureSetSpec, FeatureRow row) { RedisKey.Builder builder = - RedisKey.newBuilder().setFeatureSet(getFeatureSetReference(featureSet)); - featureSet - .getSpec() + RedisKey.newBuilder().setFeatureSet(getFeatureSetReference(featureSetSpec)); + featureSetSpec .getEntitiesList() .forEach( entityField -> @@ -332,6 +332,63 @@ static void start(int zookeeperPort, String zookeeperDataDir) { } } + // Modified version of + // https://github.com/tim-group/java-statsd-client/blob/master/src/test/java/com/timgroup/statsd/NonBlockingStatsDClientTest.java + @SuppressWarnings("CatchMayIgnoreException") + public static class DummyStatsDServer { + + private final List messagesReceived = new ArrayList(); + private final DatagramSocket server; + + public DummyStatsDServer(int port) { + try { + server = new DatagramSocket(port); + } catch (SocketException e) { + throw new IllegalStateException(e); + } + new Thread( + () -> { + try { + while (true) { + final DatagramPacket packet = new DatagramPacket(new byte[65535], 65535); + server.receive(packet); + messagesReceived.add( + new String(packet.getData(), StandardCharsets.UTF_8).trim() + "\n"); + // The sleep duration here is shorter than that used in waitForMessage() at + // 50ms. + // Otherwise sometimes some messages seem to be lost, leading to flaky tests. + Thread.sleep(15L); + } + + } catch (Exception e) { + } + }) + .start(); + } + + public void stop() { + server.close(); + } + + public void waitForMessage() { + while (messagesReceived.isEmpty()) { + try { + Thread.sleep(50L); + } catch (InterruptedException e) { + } + } + } + + public List messagesReceived() { + List out = new ArrayList<>(); + for (String msg : messagesReceived) { + String[] lines = msg.split("\n"); + out.addAll(Arrays.asList(lines)); + } + return out; + } + } + /** * Create a field object with given name and type. * @@ -389,7 +446,9 @@ public static void waitUntilAllElementsAreWrittenToStore( } String writeToStoreMetric = - WriteToStore.METRIC_NAMESPACE + ":" + WriteToStore.ELEMENTS_WRITTEN_METRIC; + WriteSuccessMetricsTransform.METRIC_NAMESPACE + + ":" + + WriteSuccessMetricsTransform.ELEMENTS_WRITTEN_METRIC; long committed = 0; long maxSystemTimeMillis = System.currentTimeMillis() + maxWaitDuration.getMillis(); diff --git a/ingestion/src/test/proto/DriverArea.proto b/ingestion/src/test/proto/DriverArea.proto deleted file mode 100644 index fee838b9e17..00000000000 --- a/ingestion/src/test/proto/DriverArea.proto +++ /dev/null @@ -1,10 +0,0 @@ -syntax = "proto3"; - -package feast; - -option java_outer_classname = "DriverAreaProto"; - -message DriverArea { - int32 driverId = 1; - int32 areaId = 2; -} \ No newline at end of file diff --git a/ingestion/src/test/proto/Ping.proto b/ingestion/src/test/proto/Ping.proto deleted file mode 100644 index b1069afa5bd..00000000000 --- a/ingestion/src/test/proto/Ping.proto +++ /dev/null @@ -1,12 +0,0 @@ -syntax = "proto3"; - -package feast; -import "google/protobuf/timestamp.proto"; - -option java_outer_classname = "PingProto"; - -message Ping { - double lat = 1; - double lng = 2; - google.protobuf.Timestamp timestamp = 3; -} diff --git a/ingestion/src/test/resources/feast/ingestion/transform/WriteFeatureValueMetricsDoFnTest.README b/ingestion/src/test/resources/feast/ingestion/transform/WriteFeatureValueMetricsDoFnTest.README new file mode 100644 index 00000000000..3c8759d1702 --- /dev/null +++ b/ingestion/src/test/resources/feast/ingestion/transform/WriteFeatureValueMetricsDoFnTest.README @@ -0,0 +1,9 @@ +WriteFeatureValueMetricsDoFnTest.input file contains data that can be read by test utility +into map of FeatureSetRef -> [FeatureRow]. In the first row, the cell value corresponds to the +field name in the FeatureRow. This should not be changed as the test utility derives the value +type from this name. Empty value in the cell is a value that is not set. For list type, the values +of different element is separated by the '|' character. + +WriteFeatureValueMetricsDoFnTest.output file contains lines of expected StatsD metrics that should +be sent when WriteFeatureValueMetricsDoFn runs. It can be checked against the actual outputted +StatsD metrics to test for correctness. diff --git a/ingestion/src/test/resources/feast/ingestion/transform/WriteFeatureValueMetricsDoFnTest.input b/ingestion/src/test/resources/feast/ingestion/transform/WriteFeatureValueMetricsDoFnTest.input new file mode 100644 index 00000000000..42731b9fe1c --- /dev/null +++ b/ingestion/src/test/resources/feast/ingestion/transform/WriteFeatureValueMetricsDoFnTest.input @@ -0,0 +1,4 @@ +featuresetref,int32,int64,double,float,bool,int32list,int64list,doublelist,floatlist,boollist,bytes,byteslist,string,stringlist +project/featureset,1,5,8,5,true,1|4|3,5|1|12,5|7|3,-2.0,true|false,,,, +project/featureset,5,-10,8,10.0,true,1|12|5,,,-1.0|-3.0,false|true,,,, +project/featureset,6,-4,8,0.0,true,2,2|5,,,true|false,,,, \ No newline at end of file diff --git a/ingestion/src/test/resources/feast/ingestion/transform/WriteFeatureValueMetricsDoFnTest.output b/ingestion/src/test/resources/feast/ingestion/transform/WriteFeatureValueMetricsDoFnTest.output new file mode 100644 index 00000000000..12ed4b7e1f2 --- /dev/null +++ b/ingestion/src/test/resources/feast/ingestion/transform/WriteFeatureValueMetricsDoFnTest.output @@ -0,0 +1,66 @@ +feast_ingestion.feature_value_min:1|g|#ingestion_job_name:job,feast_feature_name:int32,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_max:6|g|#ingestion_job_name:job,feast_feature_name:int32,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_mean:4|g|#ingestion_job_name:job,feast_feature_name:int32,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_percentile_50:5|g|#ingestion_job_name:job,feast_feature_name:int32,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_percentile_90:6|g|#ingestion_job_name:job,feast_feature_name:int32,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store + +feast_ingestion.feature_value_min:0|g|#ingestion_job_name:job,feast_feature_name:int64,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_min:-10|g|#ingestion_job_name:job,feast_feature_name:int64,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_max:5|g|#ingestion_job_name:job,feast_feature_name:int64,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_mean:0|g|#ingestion_job_name:job,feast_feature_name:int64,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_mean:-3|g|#ingestion_job_name:job,feast_feature_name:int64,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_percentile_50:-4|g|#ingestion_job_name:job,feast_feature_name:int64,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_percentile_90:5|g|#ingestion_job_name:job,feast_feature_name:int64,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store + +feast_ingestion.feature_value_min:8|g|#ingestion_job_name:job,feast_feature_name:double,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_max:8|g|#ingestion_job_name:job,feast_feature_name:double,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_mean:8|g|#ingestion_job_name:job,feast_feature_name:double,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_percentile_50:8|g|#ingestion_job_name:job,feast_feature_name:double,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_percentile_90:8|g|#ingestion_job_name:job,feast_feature_name:double,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store + +feast_ingestion.feature_value_min:0|g|#ingestion_job_name:job,feast_feature_name:float,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_max:10|g|#ingestion_job_name:job,feast_feature_name:float,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_mean:5|g|#ingestion_job_name:job,feast_feature_name:float,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_percentile_50:5|g|#ingestion_job_name:job,feast_feature_name:float,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_percentile_90:10|g|#ingestion_job_name:job,feast_feature_name:float,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store + +feast_ingestion.feature_value_min:1|g|#ingestion_job_name:job,feast_feature_name:bool,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_max:1|g|#ingestion_job_name:job,feast_feature_name:bool,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_mean:1|g|#ingestion_job_name:job,feast_feature_name:bool,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_percentile_50:1|g|#ingestion_job_name:job,feast_feature_name:bool,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_percentile_90:1|g|#ingestion_job_name:job,feast_feature_name:bool,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store + +feast_ingestion.feature_value_min:1|g|#ingestion_job_name:job,feast_feature_name:int32list,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_max:12|g|#ingestion_job_name:job,feast_feature_name:int32list,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_mean:4|g|#ingestion_job_name:job,feast_feature_name:int32list,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_percentile_50:3|g|#ingestion_job_name:job,feast_feature_name:int32list,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_percentile_90:12|g|#ingestion_job_name:job,feast_feature_name:int32list,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store + +feast_ingestion.feature_value_min:1|g|#ingestion_job_name:job,feast_feature_name:int64list,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_max:12|g|#ingestion_job_name:job,feast_feature_name:int64list,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_mean:5|g|#ingestion_job_name:job,feast_feature_name:int64list,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_percentile_50:5|g|#ingestion_job_name:job,feast_feature_name:int64list,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_percentile_90:12|g|#ingestion_job_name:job,feast_feature_name:int64list,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store + +feast_ingestion.feature_value_min:3|g|#ingestion_job_name:job,feast_feature_name:doublelist,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_max:7|g|#ingestion_job_name:job,feast_feature_name:doublelist,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_mean:5|g|#ingestion_job_name:job,feast_feature_name:doublelist,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_percentile_50:5|g|#ingestion_job_name:job,feast_feature_name:doublelist,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_percentile_90:7|g|#ingestion_job_name:job,feast_feature_name:doublelist,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store + +feast_ingestion.feature_value_min:0|g|#ingestion_job_name:job,feast_feature_name:floatlist,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_min:-3|g|#ingestion_job_name:job,feast_feature_name:floatlist,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_max:0|g|#ingestion_job_name:job,feast_feature_name:floatlist,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_max:-1|g|#ingestion_job_name:job,feast_feature_name:floatlist,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_mean:0|g|#ingestion_job_name:job,feast_feature_name:floatlist,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_mean:-2|g|#ingestion_job_name:job,feast_feature_name:floatlist,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_percentile_50:0|g|#ingestion_job_name:job,feast_feature_name:floatlist,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_percentile_50:-2|g|#ingestion_job_name:job,feast_feature_name:floatlist,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_percentile_90:0|g|#ingestion_job_name:job,feast_feature_name:floatlist,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_percentile_90:-1|g|#ingestion_job_name:job,feast_feature_name:floatlist,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store + +feast_ingestion.feature_value_min:0|g|#ingestion_job_name:job,feast_feature_name:boollist,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_max:1|g|#ingestion_job_name:job,feast_feature_name:boollist,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_mean:0.5|g|#ingestion_job_name:job,feast_feature_name:boollist,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_percentile_50:0.5|g|#ingestion_job_name:job,feast_feature_name:boollist,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_percentile_90:1|g|#ingestion_job_name:job,feast_feature_name:boollist,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store \ No newline at end of file diff --git a/ingestion/src/test/resources/feast/ingestion/transform/WriteRowMetricsDoFnTest.input b/ingestion/src/test/resources/feast/ingestion/transform/WriteRowMetricsDoFnTest.input new file mode 100644 index 00000000000..c5543d2889d --- /dev/null +++ b/ingestion/src/test/resources/feast/ingestion/transform/WriteRowMetricsDoFnTest.input @@ -0,0 +1,4 @@ +featuresetref,int32,int64,timestamp +project/featureset,1,5,2020-03-30T06:10:38Z +project/featureset,5,8,2020-03-30T06:10:43Z +project/featureset,6,,2020-03-30T06:10:42Z \ No newline at end of file diff --git a/ingestion/src/test/resources/feast/ingestion/transform/WriteRowMetricsDoFnTest.output b/ingestion/src/test/resources/feast/ingestion/transform/WriteRowMetricsDoFnTest.output new file mode 100644 index 00000000000..954215764f3 --- /dev/null +++ b/ingestion/src/test/resources/feast/ingestion/transform/WriteRowMetricsDoFnTest.output @@ -0,0 +1,23 @@ +feast_ingestion.feature_row_ingested_count:3|c|#ingestion_job_name:job,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_row_lag_ms_min:2000|g|#ingestion_job_name:job,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_row_lag_ms_max:7000|g|#ingestion_job_name:job,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_row_lag_ms_mean:4000|g|#ingestion_job_name:job,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_row_lag_ms_percentile_90:7000|g|#ingestion_job_name:job,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_row_lag_ms_percentile_95:7000|g|#ingestion_job_name:job,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_row_lag_ms_percentile_99:7000|g|#ingestion_job_name:job,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store + +feast_ingestion.feature_value_lag_ms_min:2000|g|#feast_feature_name:int32,ingestion_job_name:job,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_lag_ms_max:7000|g|#feast_feature_name:int32,ingestion_job_name:job,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_lag_ms_mean:4000|g|#feast_feature_name:int32,ingestion_job_name:job,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_lag_ms_percentile_90:7000|g|#feast_feature_name:int32,ingestion_job_name:job,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_lag_ms_percentile_95:7000|g|#feast_feature_name:int32,ingestion_job_name:job,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_lag_ms_percentile_99:7000|g|#feast_feature_name:int32,ingestion_job_name:job,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_missing_count:0|c|#feast_feature_name:int32,ingestion_job_name:job,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store + +feast_ingestion.feature_value_lag_ms_min:2000|g|#feast_feature_name:int64,ingestion_job_name:job,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_lag_ms_max:7000|g|#feast_feature_name:int64,ingestion_job_name:job,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_lag_ms_mean:4500|g|#feast_feature_name:int64,ingestion_job_name:job,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_lag_ms_percentile_90:7000|g|#feast_feature_name:int64,ingestion_job_name:job,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_lag_ms_percentile_95:7000|g|#feast_feature_name:int64,ingestion_job_name:job,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_lag_ms_percentile_99:7000|g|#feast_feature_name:int64,ingestion_job_name:job,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store +feast_ingestion.feature_value_missing_count:1|c|#feast_feature_name:int64,ingestion_job_name:job,feast_featureSet_name:featureset,feast_project_name:project,feast_store:store \ No newline at end of file diff --git a/pom.xml b/pom.xml index 05fb701ac44..fe849fcf8f2 100644 --- a/pom.xml +++ b/pom.xml @@ -28,15 +28,19 @@ pom + datatypes/java + storage/api + storage/connectors ingestion core serving sdk/java + docs/coverage/java - 0.4.2-SNAPSHOT - https://github.com/gojek/feast + 0.5 + https://github.com/feast-dev/feast UTF-8 UTF-8 @@ -54,6 +58,8 @@ 2.28.2 0.21.0 + + 2.12.1 @@ -81,7 +87,7 @@ ${github.url} scm:git:${github.url}.git - scm:git:git@github.com:gojek/feast.git + scm:git:git@github.com:feast-dev/feast.git HEAD @@ -140,6 +146,11 @@ + + io.grpc + grpc-core + ${grpcVersion} + io.grpc grpc-netty @@ -260,6 +271,31 @@ + + org.apache.logging.log4j + log4j-api + ${log4jVersion} + + + org.apache.logging.log4j + log4j-core + ${log4jVersion} + + + org.apache.logging.log4j + log4j-jul + ${log4jVersion} + + + org.apache.logging.log4j + log4j-web + ${log4jVersion} + + + org.apache.logging.log4j + log4j-slf4j-impl + ${log4jVersion} + + + spotless-check + process-test-classes + + check + + + org.apache.maven.plugins maven-compiler-plugin - 3.8.1 - 1.8 - 1.8 - - -Xlint:all - - - -Xdoclint:-syntax - + 11 org.apache.maven.plugins maven-enforcer-plugin 3.0.0-M2 + + + org.codehaus.mojo + extra-enforcer-rules + 1.2 + + valid-build-environment @@ -378,10 +434,10 @@ - [3.5,4.0) + [3.6,4.0) - [1.8,1.9) + [11.0,) @@ -416,9 +472,9 @@ org.apache.maven.plugins maven-surefire-plugin - 2.22.1 + 3.0.0-M4 - -Xms2048m -Xmx2048m -Djdk.net.URLClassPath.disableClassPathURLCheck=true + @{argLine} -Xms2048m -Xmx2048m -Djdk.net.URLClassPath.disableClassPathURLCheck=true IntegrationTest @@ -431,6 +487,14 @@ true + + + build-info + + build-info + + + @@ -513,10 +577,35 @@ docker-maven-plugin 0.20.1 + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + + -Xlint:all + -Xdoclint:all + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.1.1 + + + + org.apache.maven.shared + maven-dependency-analyzer + 1.11.1 + + + org.apache.maven.plugins maven-javadoc-plugin - 3.1.0 + 3.1.1 org.codehaus.mojo @@ -526,6 +615,18 @@ false + + org.jacoco + jacoco-maven-plugin + 0.8.5 + + + + prepare-agent + + + + org.springframework.boot spring-boot-maven-plugin @@ -542,25 +643,6 @@ org.xolstice.maven.plugins protobuf-maven-plugin 0.6.1 - - true - - com.google.protobuf:protoc:${protocVersion}:exe:${os.detected.classifier} - - grpc-java - - io.grpc:protoc-gen-grpc-java:${grpcVersion}:exe:${os.detected.classifier} - - - - - - compile - compile-custom - test-compile - - - diff --git a/protos/Makefile b/protos/Makefile deleted file mode 100644 index 5418d85d20f..00000000000 --- a/protos/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -.PHONY: go - -dirs = core serving types storage -service_dirs = core serving - -gen-go: - @$(foreach dir,$(dirs),protoc -I/usr/local/include -I. --go_out=plugins=grpc,paths=source_relative:../sdk/go/protos/ feast/$(dir)/*.proto;) - -gen-python: - pip install grpcio-tools - pip install mypy-protobuf - @$(foreach dir,$(dirs),python -m grpc_tools.protoc -I. --python_out=../sdk/python/ --mypy_out=../sdk/python/ feast/$(dir)/*.proto;) - @$(foreach dir,$(service_dirs),python -m grpc_tools.protoc -I. --grpc_python_out=../sdk/python/ feast/$(dir)/*.proto;) - -install-dependencies-docs: - mkdir -p $$HOME/bin - mkdir -p $$HOME/include - go get github.com/golang/protobuf/proto && \ - go get gopkg.in/russross/blackfriday.v2 && \ - cd $$(mktemp -d) && \ - git clone https://github.com/istio/tools/ && \ - cd tools/cmd/protoc-gen-docs && \ - go build && \ - cp protoc-gen-docs $$HOME/bin && \ - cd $$HOME && curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v3.11.2/protoc-3.11.2-linux-x86_64.zip && \ - unzip protoc-3.11.2-linux-x86_64.zip -d protoc3 && \ - mv protoc3/bin/* $$HOME/bin/ && \ - chmod +x $$HOME/bin/protoc && \ - mv protoc3/include/* $$HOME/include - -gen-docs: - protoc --docs_out=../dist/grpc feast/*/*.proto || \ - $(MAKE) install-dependencies-docs && PATH=$$HOME/bin:$$PATH protoc -I $$HOME/include/ -I . --docs_out=../dist/grpc feast/*/*.proto \ No newline at end of file diff --git a/protos/feast/core/CoreService.proto b/protos/feast/core/CoreService.proto index 35b96e17895..3cd3c756830 100644 --- a/protos/feast/core/CoreService.proto +++ b/protos/feast/core/CoreService.proto @@ -15,15 +15,16 @@ // syntax = "proto3"; - package feast.core; -option go_package = "github.com/gojek/feast/sdk/go/protos/feast/core"; +option go_package = "github.com/feast-dev/feast/sdk/go/protos/feast/core"; option java_outer_classname = "CoreServiceProto"; -option java_package = "feast.core"; +option java_package = "feast.proto.core"; import "feast/core/FeatureSet.proto"; import "feast/core/Store.proto"; +import "feast/core/FeatureSetReference.proto"; +import "feast/core/IngestionJob.proto"; service CoreService { // Retrieve version information about this Feast deployment @@ -50,8 +51,11 @@ service CoreService { // Create or update and existing feature set. // // This function is idempotent - it will not create a new feature set if schema does not change. - // If an existing feature set is updated, core will advance the version number, which will be - // returned in response. + // Schema changes will update the feature set if the changes are valid. + // All changes except the following are valid: + // - Changes to feature set id (name, project) + // - Changes to entities + // - Changes to feature name and type rpc ApplyFeatureSet (ApplyFeatureSetRequest) returns (ApplyFeatureSetResponse); // Updates core with the configuration of the store. @@ -61,8 +65,8 @@ service CoreService { rpc UpdateStore (UpdateStoreRequest) returns (UpdateStoreResponse); // Creates a project. Projects serve as namespaces within which resources like features will be - // created. Both feature set names as well as field names must be unique within a project. Project - // names themselves must be globally unique. + // created. Feature set names as must be unique within a project while field (Feature/Entity) names + // must be unique within a Feature Set. Project names themselves must be globally unique. rpc CreateProject (CreateProjectRequest) returns (CreateProjectResponse); // Archives a project. Archived projects will continue to exist and function, but won't be visible @@ -73,18 +77,33 @@ service CoreService { // Lists all projects active projects. rpc ListProjects (ListProjectsRequest) returns (ListProjectsResponse); + + // List Ingestion Jobs given an optional filter. + // Returns allow ingestions matching the given request filter. + // Returns all ingestion jobs if no filter is provided. + // Returns an empty list if no ingestion jobs match the filter. + rpc ListIngestionJobs(ListIngestionJobsRequest) returns (ListIngestionJobsResponse); + + // Restart an Ingestion Job. Restarts the ingestion job with the given job id. + // NOTE: Data might be lost during the restart for some job runners. + // Does not support stopping a job in a transitional (ie pending, suspending, aborting), + // terminal state (ie suspended or aborted) or unknown status + rpc RestartIngestionJob(RestartIngestionJobRequest) returns (RestartIngestionJobResponse); + + // Stop an Ingestion Job. Stop (Aborts) the ingestion job with the given job id. + // Does nothing if the target job if already in a terminal state (ie suspended or aborted). + // Does not support stopping a job in a transitional (ie pending, suspending, aborting) or unknown status + rpc StopIngestionJob(StopIngestionJobRequest) returns (StopIngestionJobResponse); + } // Request for a single feature set message GetFeatureSetRequest { - // Name of project the feature set belongs to (required) + // Name of project the feature set belongs to. If omitted will default to 'default' project. string project = 3; // Name of feature set (required). string name = 1; - - // Version of feature set (optional). If omitted then latest feature set will be returned. - int32 version = 2; } // Response containing a single feature set @@ -103,6 +122,7 @@ message ListFeatureSetsRequest { // If an asterisk is provided, filtering on projects will be disabled. All projects will // be matched. It is NOT possible to provide an asterisk with a string in order to do // pattern matching. + // If unspecified this field will default to the default project 'default'. string project = 3; // Name of the desired feature set. Asterisks can be used as wildcards in the name. @@ -113,15 +133,6 @@ message ListFeatureSetsRequest { // - my-feature-set* can be used to match all features prefixed by "my-feature-set" // - my-feature-set-6 can be used to select a single feature set string feature_set_name = 1; - - - // Versions of the given feature sets that will be returned. - // Valid options for version: - // "latest": only the latest version is returned. - // "*": Subscribe to all versions - // [version number]: pin to a specific version. Project and feature set name must be - // explicitly defined if a specific version is pinned. - string feature_set_version = 2; } } @@ -143,23 +154,28 @@ message ListStoresResponse { } message ApplyFeatureSetRequest { - // Feature set version and source will be ignored + // Feature set version + // If project is unspecified, will default to 'default' project. + // If project specified does not exist, the project would be automatically created. feast.core.FeatureSet feature_set = 1; } message ApplyFeatureSetResponse { + // TODO: 0 should correspond to invalid rather than NO_CHANGE enum Status { - // Latest feature set version is consistent with provided feature set + // Latest feature set is consistent with provided feature set NO_CHANGE = 0; - // New feature set or feature set version created + // New feature set created CREATED = 1; // Error occurred while trying to apply changes ERROR = 2; + + // Changes detected and updated successfully + UPDATED = 3; } - // Feature set response has been enriched with version and source information feast.core.FeatureSet feature_set = 1; Status status = 2; } @@ -215,4 +231,42 @@ message ListProjectsRequest { message ListProjectsResponse { // List of project names (archived projects are filtered out) repeated string projects = 1; -} \ No newline at end of file +} + +// Request for listing ingestion jobs +message ListIngestionJobsRequest { + Filter filter = 1; + + message Filter { + // Filter by Job ID assigned by Feast + string id = 1; + // Filter by ingestion job target feature set. + FeatureSetReference feature_set_reference = 2; + // Filter by Name of store + string store_name = 3; + } +} + +// Response from listing ingestion jobs +message ListIngestionJobsResponse { + repeated IngestionJob jobs = 1; +} + +// Request to restart ingestion job +message RestartIngestionJobRequest { + // Job ID assigned by Feast + string id = 1; +} + +// Response from restartingan injestion job +message RestartIngestionJobResponse {} + + +// Request to stop ingestion job +message StopIngestionJobRequest { + // Job ID assigned by Feast + string id = 1; +} + +// Request from stopping an ingestion job +message StopIngestionJobResponse {} diff --git a/protos/feast/core/FeatureSet.proto b/protos/feast/core/FeatureSet.proto index 910cc375f7b..b0b15276bcf 100644 --- a/protos/feast/core/FeatureSet.proto +++ b/protos/feast/core/FeatureSet.proto @@ -16,14 +16,15 @@ syntax = "proto3"; package feast.core; -option java_package = "feast.core"; +option java_package = "feast.proto.core"; option java_outer_classname = "FeatureSetProto"; -option go_package = "github.com/gojek/feast/sdk/go/protos/feast/core"; +option go_package = "github.com/feast-dev/feast/sdk/go/protos/feast/core"; import "feast/types/Value.proto"; import "feast/core/Source.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; +import "tensorflow_metadata/proto/v0/schema.proto"; message FeatureSet { // User-specified specifications of this feature set. @@ -39,8 +40,8 @@ message FeatureSetSpec { // Name of the feature set. Must be unique. string name = 1; - // Feature set version. - int32 version = 2; + // Feature set version was removed in v0.5.0. + reserved 2; // List of entities contained within this featureSet. // This allows the feature to be used during joins between feature sets. @@ -59,13 +60,16 @@ message FeatureSetSpec { // Optional. Source on which feature rows can be found. // If not set, source will be set to the default value configured in Feast Core. Source source = 6; + + // User defined metadata + map labels = 8; } message EntitySpec { // Name of the entity. string name = 1; - // Value type of the feature. + // Value type of the entity. feast.types.ValueType.Enum value_type = 2; } @@ -75,6 +79,56 @@ message FeatureSpec { // Value type of the feature. feast.types.ValueType.Enum value_type = 2; + + // Reserve field numbers 15 and below for fields that will almost always be set + // https://developers.google.com/protocol-buffers/docs/proto3#assigning-field-numbers + reserved 3 to 15; + + // Labels for user defined metadata on a feature + map labels = 16; + + // Reserved for fundamental future additions less noisy in the schema that TFDV stats fields + reserved 17 to 29; + + // presence_constraints, shape_type and domain_info are referenced from: + // https://github.com/tensorflow/metadata/blob/36f65d1268cbc92cdbcf812ee03dcf47fb53b91e/tensorflow_metadata/proto/v0/schema.proto#L107 + + oneof presence_constraints { + // Constraints on the presence of this feature in the examples. + tensorflow.metadata.v0.FeaturePresence presence = 30; + // Only used in the context of a "group" context, e.g., inside a sequence. + tensorflow.metadata.v0.FeaturePresenceWithinGroup group_presence = 31; + } + + // The shape of the feature which governs the number of values that appear in + // each example. + oneof shape_type { + // The feature has a fixed shape corresponding to a multi-dimensional + // tensor. + tensorflow.metadata.v0.FixedShape shape = 32; + // The feature doesn't have a well defined shape. All we know are limits on + // the minimum and maximum number of values. + tensorflow.metadata.v0.ValueCount value_count = 33; + } + + // Domain for the values of the feature. + oneof domain_info { + // Reference to a domain defined at the schema level. + string domain = 34; + // Inline definitions of domains. + tensorflow.metadata.v0.IntDomain int_domain = 35; + tensorflow.metadata.v0.FloatDomain float_domain = 36; + tensorflow.metadata.v0.StringDomain string_domain = 37; + tensorflow.metadata.v0.BoolDomain bool_domain = 38; + tensorflow.metadata.v0.StructDomain struct_domain = 39; + // Supported semantic domains. + tensorflow.metadata.v0.NaturalLanguageDomain natural_language_domain = 40; + tensorflow.metadata.v0.ImageDomain image_domain = 41; + tensorflow.metadata.v0.MIDDomain mid_domain = 42; + tensorflow.metadata.v0.URLDomain url_domain = 43; + tensorflow.metadata.v0.TimeDomain time_domain = 44; + tensorflow.metadata.v0.TimeOfDayDomain time_of_day_domain = 45; + } } message FeatureSetMeta { @@ -93,5 +147,6 @@ message FeatureSetMeta { enum FeatureSetStatus { STATUS_INVALID = 0; STATUS_PENDING = 1; + STATUS_JOB_STARTING = 3; STATUS_READY = 2; } diff --git a/protos/feast/core/FeatureSetReference.proto b/protos/feast/core/FeatureSetReference.proto new file mode 100644 index 00000000000..85762512ca6 --- /dev/null +++ b/protos/feast/core/FeatureSetReference.proto @@ -0,0 +1,33 @@ +// +// Copyright 2020 The Feast Authors +// +// 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 feast.core; + +option go_package = "github.com/feast-dev/feast/sdk/go/protos/feast/core"; +option java_outer_classname = "FeatureSetReferenceProto"; +option java_package = "feast.proto.core"; + +// Defines a composite key that refers to a unique FeatureSet +message FeatureSetReference { + // Name of the project + string project = 1; + // Name of the FeatureSet + string name = 2; + // Feature set version was removed in v0.5.0. + reserved 3; +} diff --git a/protos/feast/core/IngestionJob.proto b/protos/feast/core/IngestionJob.proto new file mode 100644 index 00000000000..c63e573ffea --- /dev/null +++ b/protos/feast/core/IngestionJob.proto @@ -0,0 +1,65 @@ +// +// Copyright 2020 The Feast Authors +// +// 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 feast.core; + +option go_package = "github.com/feast-dev/feast/sdk/go/protos/feast/core"; +option java_outer_classname = "IngestionJobProto"; +option java_package = "feast.proto.core"; + +import "feast/core/FeatureSet.proto"; +import "feast/core/Store.proto"; +import "feast/core/Source.proto"; + +// Represents Feast Injestion Job +message IngestionJob { + // Job ID assigned by Feast + string id = 1; + // External job ID specific to the runner. + // For DirectRunner jobs, this is identical to id. For DataflowRunner jobs, this refers to the Dataflow job ID. + string external_id = 2; + IngestionJobStatus status = 3; + // List of feature sets whose features are populated by this job. + repeated feast.core.FeatureSet feature_sets = 4; + // Source this job is reading from. + feast.core.Source source = 5; + // Store this job is writing to. + feast.core.Store store = 6; +} + +// Status of a Feast Ingestion Job +enum IngestionJobStatus { + // Job status is not known. + UNKNOWN = 0; + // Import job is submitted to runner and currently pending for executing + PENDING = 1; + // Import job is currently running in the runner + RUNNING = 2; + // Runner's reported the import job has completed (applicable to batch job) + COMPLETED = 3; + // When user sent abort command, but it's still running + ABORTING = 4; + // User initiated abort job + ABORTED = 5; + // Runner's reported that the import job failed to run or there is a failure during job + ERROR = 6; + // job has been suspended and waiting for cleanup + SUSPENDING = 7; + // job has been suspended + SUSPENDED = 8; +} diff --git a/protos/feast/core/Runner.proto b/protos/feast/core/Runner.proto new file mode 100644 index 00000000000..544972286df --- /dev/null +++ b/protos/feast/core/Runner.proto @@ -0,0 +1,73 @@ +// +// * Copyright 2020 The Feast Authors +// * +// * 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 feast.core; + +option java_package = "feast.proto.core"; +option java_outer_classname = "RunnerProto"; +option go_package = "github.com/feast-dev/feast/sdk/go/protos/feast/core"; + +message DirectRunnerConfigOptions { + /** + * Controls the amount of target parallelism the DirectRunner will use. + * Defaults to the greater of the number of available processors and 3. Must be a value + * greater than zero. + */ + int32 targetParallelism = 1; + + /* BigQuery table specification, e.g. PROJECT_ID:DATASET_ID.PROJECT_ID */ + string deadLetterTableSpec = 2; +} + +message DataflowRunnerConfigOptions { + /* Project id to use when launching jobs. */ + string project = 1; + + /* The Google Compute Engine region for creating Dataflow jobs. */ + string region = 2; + + /* GCP availability zone for operations. */ + string zone = 3; + + /* Run the job as a specific service account, instead of the default GCE robot. */ + string serviceAccount = 4; + + /* GCE network for launching workers. */ + string network = 5; + + /* GCE subnetwork for launching workers. e.g. regions/asia-east1/subnetworks/mysubnetwork */ + string subnetwork = 6; + + /* Machine type to create Dataflow worker VMs as. */ + string workerMachineType = 7; + + /* The autoscaling algorithm to use for the workerpool. */ + string autoscalingAlgorithm = 8; + + /* Specifies whether worker pools should be started with public IP addresses. */ + bool usePublicIps = 9; + + // A pipeline level default location for storing temporary files. Support Google Cloud Storage locations, + // e.g. gs://bucket/object + string tempLocation = 10; + + /* The maximum number of workers to use for the workerpool. */ + int32 maxNumWorkers = 11; + + /* BigQuery table specification, e.g. PROJECT_ID:DATASET_ID.PROJECT_ID */ + string deadLetterTableSpec = 12; +} \ No newline at end of file diff --git a/protos/feast/core/Source.proto b/protos/feast/core/Source.proto index 8a6cbd415a2..9dcbf2fa056 100644 --- a/protos/feast/core/Source.proto +++ b/protos/feast/core/Source.proto @@ -17,9 +17,9 @@ syntax = "proto3"; package feast.core; -option java_package = "feast.core"; +option java_package = "feast.proto.core"; option java_outer_classname = "SourceProto"; -option go_package = "github.com/gojek/feast/sdk/go/protos/feast/core"; +option go_package = "github.com/feast-dev/feast/sdk/go/protos/feast/core"; message Source { @@ -39,9 +39,15 @@ enum SourceType { } message KafkaSourceConfig { - // - bootstrapServers: [comma delimited value of host[:port]] + // Comma separated list of Kafka bootstrap servers. Used for feature sets without a defined source host[:port]] string bootstrap_servers = 1; - // - topics: [Kafka topic name. This value is provisioned by core and should not be set by the user.] + // Kafka topic to use for feature sets without user defined topics string topic = 2; + + // Number of Kafka partitions to to use for managed feature stream. + int32 partitions = 3; + + // Defines the number of copies of managed feature stream Kafka. + int32 replicationFactor = 4; } \ No newline at end of file diff --git a/protos/feast/core/Store.proto b/protos/feast/core/Store.proto index 931a9d46b69..7279c484eea 100644 --- a/protos/feast/core/Store.proto +++ b/protos/feast/core/Store.proto @@ -17,9 +17,9 @@ syntax = "proto3"; package feast.core; -option java_package = "feast.core"; +option java_package = "feast.proto.core"; option java_outer_classname = "StoreProto"; -option go_package = "github.com/gojek/feast/sdk/go/protos/feast/core"; +option go_package = "github.com/feast-dev/feast/sdk/go/protos/feast/core"; // Store provides a location where Feast reads and writes feature values. // Feature values will be written to the Store in the form of FeatureRow elements. @@ -48,12 +48,7 @@ message Store { // BigQuery stores a FeatureRow element as a row in a BigQuery table. // - // Table name is derived from the feature set name and version as: - // [feature_set_name]_v[feature_set_version] - // - // For example: - // A feature row for feature set "driver" and version "1" will be written - // to table "driver_v1". + // Table name is derived is the same as the feature set name. // // The entities and features in a FeatureSetSpec corresponds to the // fields in the BigQuery table (these make up the BigQuery schema). @@ -69,15 +64,11 @@ message Store { // ====================|==================|================================ // - event_timestamp | TIMESTAMP | event time of the FeatureRow // - created_timestamp | TIMESTAMP | processing time of the ingestion of the FeatureRow + // - ingestion_id | STRING | unique id identifying groups of rows that have been ingested together // - job_id | STRING | identifier for the job that writes the FeatureRow to the corresponding BigQuery table // // BigQuery table created will be partitioned by the field "event_timestamp" // of the FeatureRow (https://cloud.google.com/bigquery/docs/partitioned-tables). - // - // Since newer version of feature set can introduce breaking, non backward- - // compatible BigQuery schema updates, incrementing the version of a - // feature set will result in the creation of a new empty BigQuery table - // with the new schema. // // The following table shows how ValueType in Feast is mapped to // BigQuery Standard SQL data types @@ -105,6 +96,8 @@ message Store { // Unsupported in Feast 0.3 CASSANDRA = 3; + + REDIS_CLUSTER = 4; } message RedisConfig { @@ -120,6 +113,9 @@ message Store { message BigQueryConfig { string project_id = 1; string dataset_id = 2; + string staging_location = 3; + int32 initial_retry_delay_seconds = 4; + int32 total_timeout_seconds = 5; } message CassandraConfig { @@ -127,6 +123,13 @@ message Store { int32 port = 2; } + message RedisClusterConfig { + // List of Redis Uri for all the nodes in Redis Cluster, comma separated. Eg. host1:6379, host2:6379 + string connection_string = 1; + int32 initial_backoff_ms = 2; + int32 max_retries = 3; + } + message Subscription { // Name of project that the feature sets belongs to. This can be one of // - [project_name] @@ -136,7 +139,6 @@ message Store { // pattern matching. string project = 3; - // Name of the desired feature set. Asterisks can be used as wildcards in the name. // Matching on names is only permitted if a specific project is defined. It is disallowed // If the project name is set to "*" @@ -146,13 +148,8 @@ message Store { // - my-feature-set-6 can be used to select a single feature set string name = 1; - // Versions of the given feature sets that will be returned. - // Valid options for version: - // "latest": only the latest version is returned. - // "*": Subscribe to all versions - // [version number]: pin to a specific version. Project and feature set name must be - // explicitly defined if a specific version is pinned. - string version = 2; + // Feature set version was removed in v0.5.0. + reserved 2; } // Name of the store. @@ -169,5 +166,6 @@ message Store { RedisConfig redis_config = 11; BigQueryConfig bigquery_config = 12; CassandraConfig cassandra_config = 13; + RedisClusterConfig redis_cluster_config = 14; } } diff --git a/protos/feast/serving/ServingService.proto b/protos/feast/serving/ServingService.proto index 5145670ec9a..cd7d51bd59c 100644 --- a/protos/feast/serving/ServingService.proto +++ b/protos/feast/serving/ServingService.proto @@ -19,12 +19,11 @@ syntax = "proto3"; package feast.serving; import "google/protobuf/timestamp.proto"; -import "google/protobuf/duration.proto"; import "feast/types/Value.proto"; -option java_package = "feast.serving"; +option java_package = "feast.proto.serving"; option java_outer_classname = "ServingAPIProto"; -option go_package = "github.com/gojek/feast/sdk/go/protos/feast/serving"; +option go_package = "github.com/feast-dev/feast/sdk/go/protos/feast/serving"; service ServingService { // Get information about this Feast serving. @@ -63,21 +62,19 @@ message GetFeastServingInfoResponse { } message FeatureReference { - // Project name + // Project name. This field is optional, if unspecified will default to 'default'. string project = 1; // Feature name string name = 2; - // Feature version - int32 version = 3; + // Feature set name specifying the feature set of this referenced feature. + // This field is optional if the feature referenced is unique across the project + // in which case the feature set would be automatically infered + string feature_set = 5; - // The features will be retrieved if: - // entity_timestamp - max_age <= event_timestamp <= entity_timestamp - // - // If unspecified the default max_age specified in FeatureSetSpec will - // be used. - google.protobuf.Duration max_age = 4; + // Feature version and max_age was removed in v0.5.0 + reserved 3, 4; } message GetOnlineFeaturesRequest { diff --git a/protos/feast/storage/Redis.proto b/protos/feast/storage/Redis.proto index ae287f4e6bf..04052aa800c 100644 --- a/protos/feast/storage/Redis.proto +++ b/protos/feast/storage/Redis.proto @@ -21,17 +21,18 @@ import "feast/types/Field.proto"; package feast.storage; option java_outer_classname = "RedisProto"; -option java_package = "feast.storage"; -option go_package = "github.com/gojek/feast/sdk/go/protos/feast/storage"; +option java_package = "feast.proto.storage"; +option go_package = "github.com/feast-dev/feast/sdk/go/protos/feast/storage"; message RedisKey { // Field number 1 is reserved for a future distributing hash if needed // (for when redis is clustered). - // FeatureSet this row belongs to, this is defined as featureSetName:version. + // FeatureSet this row belongs to, this is defined as featureSetName. string feature_set = 2; // List of fields containing entity names and their respective values - // contained within this feature row. + // contained within this feature row. The entities should be sorted + // by the entity name alphabetically in ascending order. repeated feast.types.Field entities = 3; } diff --git a/protos/feast/types/FeatureRow.proto b/protos/feast/types/FeatureRow.proto index 24293c6faa6..fd8a561c7bc 100644 --- a/protos/feast/types/FeatureRow.proto +++ b/protos/feast/types/FeatureRow.proto @@ -21,9 +21,9 @@ import "feast/types/Field.proto"; package feast.types; -option java_package = "feast.types"; +option java_package = "feast.proto.types"; option java_outer_classname = "FeatureRowProto"; -option go_package = "github.com/gojek/feast/sdk/go/protos/feast/types"; +option go_package = "github.com/feast-dev/feast/sdk/go/protos/feast/types"; message FeatureRow { @@ -36,7 +36,10 @@ message FeatureRow { google.protobuf.Timestamp event_timestamp = 3; // Complete reference to the featureSet this featureRow belongs to, in the form of - // featureSetName:version. This value will be used by the feast ingestion job to filter + // /. This value will be used by the feast ingestion job to filter // rows, and write the values to the correct tables. string feature_set = 6; -} \ No newline at end of file + + // Identifier tying this feature row to a specific ingestion job. + string ingestion_id = 7; +} diff --git a/protos/feast/types/FeatureRowExtended.proto b/protos/feast/types/FeatureRowExtended.proto index e88fbb73232..f922fe66bfd 100644 --- a/protos/feast/types/FeatureRowExtended.proto +++ b/protos/feast/types/FeatureRowExtended.proto @@ -21,9 +21,9 @@ import "feast/types/FeatureRow.proto"; package feast.types; -option java_package = "feast.types"; +option java_package = "feast.proto.types"; option java_outer_classname = "FeatureRowExtendedProto"; -option go_package = "github.com/gojek/feast/sdk/go/protos/feast/types"; +option go_package = "github.com/feast-dev/feast/sdk/go/protos/feast/types"; message Error { string cause = 1; // exception class name diff --git a/protos/feast/types/Field.proto b/protos/feast/types/Field.proto index 3929b16a322..3b8416c253a 100644 --- a/protos/feast/types/Field.proto +++ b/protos/feast/types/Field.proto @@ -20,9 +20,9 @@ import "feast/types/Value.proto"; package feast.types; -option java_package = "feast.types"; +option java_package = "feast.proto.types"; option java_outer_classname = "FieldProto"; -option go_package = "github.com/gojek/feast/sdk/go/protos/feast/types"; +option go_package = "github.com/feast-dev/feast/sdk/go/protos/feast/types"; message Field { string name = 1; diff --git a/protos/feast/types/Value.proto b/protos/feast/types/Value.proto index 065497f30a3..b0beabd7969 100644 --- a/protos/feast/types/Value.proto +++ b/protos/feast/types/Value.proto @@ -18,9 +18,9 @@ syntax = "proto3"; package feast.types; -option java_package = "feast.types"; +option java_package = "feast.proto.types"; option java_outer_classname = "ValueProto"; -option go_package = "github.com/gojek/feast/sdk/go/protos/feast/types"; +option go_package = "github.com/feast-dev/feast/sdk/go/protos/feast/types"; message ValueType { enum Enum { diff --git a/protos/tensorflow_metadata/proto/v0/path.proto b/protos/tensorflow_metadata/proto/v0/path.proto new file mode 100644 index 00000000000..3a4e41bad91 --- /dev/null +++ b/protos/tensorflow_metadata/proto/v0/path.proto @@ -0,0 +1,44 @@ +// Copyright 2018 The TensorFlow Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================= + +syntax = "proto2"; +option cc_enable_arenas = true; + +package tensorflow.metadata.v0; + +option java_package = "org.tensorflow.metadata.v0"; +option java_multiple_files = true; +option go_package = "github.com/feast-dev/feast/sdk/go/protos/tensorflow_metadata/proto/v0"; + +// A path is a more general substitute for the name of a field or feature that +// can be used for flat examples as well as structured data. For example, if +// we had data in a protocol buffer: +// message Person { +// int age = 1; +// optional string gender = 2; +// repeated Person parent = 3; +// } +// Thus, here the path {step:["parent", "age"]} in statistics would refer to the +// age of a parent, and {step:["parent", "parent", "age"]} would refer to the +// age of a grandparent. This allows us to distinguish between the statistics +// of parents' ages and grandparents' ages. In general, repeated messages are +// to be preferred to linked lists of arbitrary length. +// For SequenceExample, if we have a feature list "foo", this is represented +// by {step:["##SEQUENCE##", "foo"]}. +message Path { + // Any string is a valid step. + // However, whenever possible have a step be [A-Za-z0-9_]+. + repeated string step = 1; +} diff --git a/protos/tensorflow_metadata/proto/v0/schema.proto b/protos/tensorflow_metadata/proto/v0/schema.proto new file mode 100644 index 00000000000..00005ee913d --- /dev/null +++ b/protos/tensorflow_metadata/proto/v0/schema.proto @@ -0,0 +1,673 @@ +// Copyright 2017 The TensorFlow Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================= + +syntax = "proto2"; + +package tensorflow.metadata.v0; + +import "google/protobuf/any.proto"; +import "tensorflow_metadata/proto/v0/path.proto"; + +option cc_enable_arenas = true; +option java_package = "org.tensorflow.metadata.v0"; +option java_multiple_files = true; +option go_package = "github.com/feast-dev/feast/sdk/go/protos/tensorflow_metadata/proto/v0"; + +// LifecycleStage. Only UNKNOWN_STAGE, BETA, and PRODUCTION features are +// actually validated. +// PLANNED, ALPHA, and DEBUG are treated as DEPRECATED. +enum LifecycleStage { + UNKNOWN_STAGE = 0; // Unknown stage. + PLANNED = 1; // Planned feature, may not be created yet. + ALPHA = 2; // Prototype feature, not used in experiments yet. + BETA = 3; // Used in user-facing experiments. + PRODUCTION = 4; // Used in a significant fraction of user traffic. + DEPRECATED = 5; // No longer supported: do not use in new models. + DEBUG_ONLY = 6; // Only exists for debugging purposes. +} + +// +// Message to represent schema information. +// NextID: 14 +message Schema { + // Features described in this schema. + repeated Feature feature = 1; + + // Sparse features described in this schema. + repeated SparseFeature sparse_feature = 6; + + // Weighted features described in this schema. + repeated WeightedFeature weighted_feature = 12; + + // Use StructDomain instead. + // Sequences described in this schema. A sequence may be described in terms of + // several features. Any features appearing within a sequence must *not* be + // declared as top-level features in . +// GOOGLE-LEGACY repeated Sequence sequence = 2; + + // declared as top-level features in . + // String domains referenced in the features. + repeated StringDomain string_domain = 4; + + // top level float domains that can be reused by features + repeated FloatDomain float_domain = 9; + + // top level int domains that can be reused by features + repeated IntDomain int_domain = 10; + + // Default environments for each feature. + // An environment represents both a type of location (e.g. a server or phone) + // and a time (e.g. right before model X is run). In the standard scenario, + // 99% of the features should be in the default environments TRAINING, + // SERVING, and the LABEL (or labels) AND WEIGHT is only available at TRAINING + // (not at serving). + // Other possible variations: + // 1. There may be TRAINING_MOBILE, SERVING_MOBILE, TRAINING_SERVICE, + // and SERVING_SERVICE. + // 2. If one is ensembling three models, where the predictions of the first + // three models are available for the ensemble model, there may be + // TRAINING, SERVING_INITIAL, SERVING_ENSEMBLE. + // See FeatureProto::not_in_environment and FeatureProto::in_environment. + repeated string default_environment = 5; + + /* BEGIN GOOGLE-LEGACY + // TODO(b/73109633): Change default to false, before removing this field. + optional bool generate_legacy_feature_spec = 7 [default = true]; + END GOOGLE-LEGACY */ + + // Additional information about the schema as a whole. Features may also + // be annotated individually. + optional Annotation annotation = 8; + + // Dataset-level constraints. This is currently used for specifying + // information about changes in num_examples. + optional DatasetConstraints dataset_constraints = 11; + + // TensorRepresentation groups. The keys are the names of the groups. + // Key "" (empty string) denotes the "default" group, which is what should + // be used when a group name is not provided. + // See the documentation at TensorRepresentationGroup for more info. + // Under development. DO NOT USE. + map tensor_representation_group = 13; +} + +// Describes schema-level information about a specific feature. +// NextID: 31 +message Feature { + // The name of the feature. + optional string name = 1; // required + + // This field is no longer supported. Instead, use: + // lifecycle_stage: DEPRECATED + // TODO(b/111450258): remove this. + optional bool deprecated = 2 [deprecated = true]; + + // Comment field for a human readable description of the field. + // TODO(b/123518108): remove this. +// GOOGLE-LEGACY optional string comment = 3 [deprecated = true]; + + oneof presence_constraints { + // Constraints on the presence of this feature in the examples. + FeaturePresence presence = 14; + // Only used in the context of a "group" context, e.g., inside a sequence. + FeaturePresenceWithinGroup group_presence = 17; + } + + // The shape of the feature which governs the number of values that appear in + // each example. + oneof shape_type { + // The feature has a fixed shape corresponding to a multi-dimensional + // tensor. + FixedShape shape = 23; + // The feature doesn't have a well defined shape. All we know are limits on + // the minimum and maximum number of values. + ValueCount value_count = 5; + } + + // Physical type of the feature's values. + // Note that you can have: + // type: BYTES + // int_domain: { + // min: 0 + // max: 3 + // } + // This would be a field that is syntactically BYTES (i.e. strings), but + // semantically an int, i.e. it would be "0", "1", "2", or "3". + optional FeatureType type = 6; + + // Domain for the values of the feature. + oneof domain_info { + // Reference to a domain defined at the schema level. + string domain = 7; + // Inline definitions of domains. + IntDomain int_domain = 9; + FloatDomain float_domain = 10; + StringDomain string_domain = 11; + BoolDomain bool_domain = 13; + StructDomain struct_domain = 29; + // Supported semantic domains. + NaturalLanguageDomain natural_language_domain = 24; + ImageDomain image_domain = 25; + MIDDomain mid_domain = 26; + URLDomain url_domain = 27; + TimeDomain time_domain = 28; + TimeOfDayDomain time_of_day_domain = 30; + } + + // Constraints on the distribution of the feature values. + // Currently only supported for StringDomains. + // TODO(b/69473628): Extend functionality to other domain types. + optional DistributionConstraints distribution_constraints = 15; + + // Additional information about the feature for documentation purpose. + optional Annotation annotation = 16; + + // Tests comparing the distribution to the associated serving data. + optional FeatureComparator skew_comparator = 18; + + // Tests comparing the distribution between two consecutive spans (e.g. days). + optional FeatureComparator drift_comparator = 21; + + // List of environments this feature is present in. + // Should be disjoint from not_in_environment. + // This feature is in environment "foo" if: + // ("foo" is in in_environment or default_environments) AND + // "foo" is not in not_in_environment. + // See Schema::default_environments. + repeated string in_environment = 20; + + // List of environments this feature is not present in. + // Should be disjoint from of in_environment. + // See Schema::default_environments and in_environment. + repeated string not_in_environment = 19; + + // The lifecycle stage of a feature. It can also apply to its descendants. + // i.e., if a struct is DEPRECATED, its children are implicitly deprecated. + optional LifecycleStage lifecycle_stage = 22; +} + +// Additional information about the schema or about a feature. +message Annotation { + // Tags can be used to mark features. For example, tag on user_age feature can + // be `user_feature`, tag on user_country feature can be `location_feature`, + // `user_feature`. + repeated string tag = 1; + // Free-text comments. This can be used as a description of the feature, + // developer notes etc. + repeated string comment = 2; + // Application-specific metadata may be attached here. + repeated .google.protobuf.Any extra_metadata = 3; +} + +// Checks that the ratio of the current value to the previous value is not below +// the min_fraction_threshold or above the max_fraction_threshold. That is, +// previous value * min_fraction_threshold <= current value <= +// previous value * max_fraction_threshold. +// To specify that the value cannot change, set both min_fraction_threshold and +// max_fraction_threshold to 1.0. +message NumericValueComparator { + optional double min_fraction_threshold = 1; + optional double max_fraction_threshold = 2; +} + +// Constraints on the entire dataset. +message DatasetConstraints { + // Tests differences in number of examples between the current data and the + // previous span. + optional NumericValueComparator num_examples_drift_comparator = 1; + // Tests comparisions in number of examples between the current data and the + // previous version of that data. + optional NumericValueComparator num_examples_version_comparator = 2; + // Minimum number of examples in the dataset. + optional int64 min_examples_count = 3; +} + +// Specifies a fixed shape for the feature's values. The immediate implication +// is that each feature has a fixed number of values. Moreover, these values +// can be parsed in a multi-dimensional tensor using the specified axis sizes. +// The FixedShape defines a lexicographical ordering of the data. For instance, +// if there is a FixedShape { +// dim {size:3} dim {size:2} +// } +// then tensor[0][0]=field[0] +// then tensor[0][1]=field[1] +// then tensor[1][0]=field[2] +// then tensor[1][1]=field[3] +// then tensor[2][0]=field[4] +// then tensor[2][1]=field[5] +// +// The FixedShape message is identical with the TensorFlow TensorShape proto +// message. +message FixedShape { + // The dimensions that define the shape. The total number of values in each + // example is the product of sizes of each dimension. + repeated Dim dim = 2; + + // An axis in a multi-dimensional feature representation. + message Dim { + optional int64 size = 1; + + // Optional name of the tensor dimension. + optional string name = 2; + } +} + +// Limits on maximum and minimum number of values in a +// single example (when the feature is present). Use this when the minimum +// value count can be different than the maximum value count. Otherwise prefer +// FixedShape. +message ValueCount { + optional int64 min = 1; + optional int64 max = 2; +} + +/* BEGIN GOOGLE-LEGACY +// Constraint on the number of elements in a sequence. +message LengthConstraint { + optional int64 min = 1; + optional int64 max = 2; +} + +// A sequence is a logical feature that comprises several "raw" features that +// encode values at different "steps" within the sequence. +// TODO(b/110490010): Delete this. This is a special case of StructDomain. +message Sequence { + // An optional name for this sequence. Used mostly for debugging and + // presentation. + optional string name = 1; + + // Features that comprise the sequence. These features are "zipped" together + // to form the values for the sequence at different steps. + // - Use group_presence within each feature to encode presence constraints + // within the sequence. + // - If all features have the same value-count constraints then + // declare this once using the shape_constraint below. + repeated Feature feature = 2; + + // Constraints on the presence of the sequence across all examples in the + // dataset. The sequence is assumed to be present if at least one of its + // features is present. + optional FeaturePresence presence = 3; + + // Shape constraints that apply on all the features that comprise the + // sequence. If this is set then the value_count in 'feature' is + // ignored. + // TODO(martinz): delete: there is no reason to believe the shape of the + // fields in a sequence will be the same. Use the fields in Feature instead. + oneof shape_constraint { + ValueCount value_count = 4; + FixedShape fixed_shape = 5; + } + + // Constraint on the number of elements in a sequence. + optional LengthConstraint length_constraint = 6; +} +END GOOGLE-LEGACY */ + +// Represents a weighted feature that is encoded as a combination of raw base +// features. The `weight_feature` should be a float feature with identical +// shape as the `feature`. This is useful for representing weights associated +// with categorical tokens (e.g. a TFIDF weight associated with each token). +// TODO(b/142122960): Handle WeightedCategorical end to end in TFX (validation, +// TFX Unit Testing, etc) +message WeightedFeature { + // Name for the weighted feature. This should not clash with other features in + // the same schema. + optional string name = 1; // required + // Path of a base feature to be weighted. Required. + optional Path feature = 2; + // Path of weight feature to associate with the base feature. Must be same + // shape as feature. Required. + optional Path weight_feature = 3; + // The lifecycle_stage determines where a feature is expected to be used, + // and therefore how important issues with it are. + optional LifecycleStage lifecycle_stage = 4; +} + +// A sparse feature represents a sparse tensor that is encoded with a +// combination of raw features, namely index features and a value feature. Each +// index feature defines a list of indices in a different dimension. +message SparseFeature { + reserved 11; + // Name for the sparse feature. This should not clash with other features in + // the same schema. + optional string name = 1; // required + + // This field is no longer supported. Instead, use: + // lifecycle_stage: DEPRECATED + // TODO(b/111450258): remove this. + optional bool deprecated = 2 [deprecated = true]; + + // The lifecycle_stage determines where a feature is expected to be used, + // and therefore how important issues with it are. + optional LifecycleStage lifecycle_stage = 7; + + // Comment field for a human readable description of the field. + // TODO(martinz): delete, convert to annotation. +// GOOGLE-LEGACY optional string comment = 3 [deprecated = true]; + + // Constraints on the presence of this feature in examples. + // Deprecated, this is inferred by the referred features. + optional FeaturePresence presence = 4 [deprecated = true]; + + // Shape of the sparse tensor that this SparseFeature represents. + // Currently not supported. + // TODO(b/109669962): Consider deriving this from the referred features. + optional FixedShape dense_shape = 5; + + // Features that represent indexes. Should be integers >= 0. + repeated IndexFeature index_feature = 6; // at least one + message IndexFeature { + // Name of the index-feature. This should be a reference to an existing + // feature in the schema. + optional string name = 1; + } + + // If true then the index values are already sorted lexicographically. + optional bool is_sorted = 8; + + optional ValueFeature value_feature = 9; // required + message ValueFeature { + // Name of the value-feature. This should be a reference to an existing + // feature in the schema. + optional string name = 1; + } + + // Type of value feature. + // Deprecated, this is inferred by the referred features. + optional FeatureType type = 10 [deprecated = true]; +} + +// Models constraints on the distribution of a feature's values. +// TODO(martinz): replace min_domain_mass with max_off_domain (but slowly). +message DistributionConstraints { + // The minimum fraction (in [0,1]) of values across all examples that + // should come from the feature's domain, e.g.: + // 1.0 => All values must come from the domain. + // .9 => At least 90% of the values must come from the domain. + optional double min_domain_mass = 1 [default = 1.0]; +} + +// Encodes information for domains of integer values. +// Note that FeatureType could be either INT or BYTES. +message IntDomain { + // Id of the domain. Required if the domain is defined at the schema level. If + // so, then the name must be unique within the schema. + optional string name = 1; + + // Min and max values for the domain. + optional int64 min = 3; + optional int64 max = 4; + + // If true then the domain encodes categorical values (i.e., ids) rather than + // ordinal values. + optional bool is_categorical = 5; +} + +// Encodes information for domains of float values. +// Note that FeatureType could be either INT or BYTES. +message FloatDomain { + // Id of the domain. Required if the domain is defined at the schema level. If + // so, then the name must be unique within the schema. + optional string name = 1; + + // Min and max values of the domain. + optional float min = 3; + optional float max = 4; +} + +// Domain for a recursive struct. +// NOTE: If a feature with a StructDomain is deprecated, then all the +// child features (features and sparse_features of the StructDomain) are also +// considered to be deprecated. Similarly child features can only be in +// environments of the parent feature. +message StructDomain { + repeated Feature feature = 1; + + repeated SparseFeature sparse_feature = 2; +} + +// Encodes information for domains of string values. +message StringDomain { + // Id of the domain. Required if the domain is defined at the schema level. If + // so, then the name must be unique within the schema. + optional string name = 1; + + // The values appearing in the domain. + repeated string value = 2; +} + +// Encodes information about the domain of a boolean attribute that encodes its +// TRUE/FALSE values as strings, or 0=false, 1=true. +// Note that FeatureType could be either INT or BYTES. +message BoolDomain { + // Id of the domain. Required if the domain is defined at the schema level. If + // so, then the name must be unique within the schema. + optional string name = 1; + + // Strings values for TRUE/FALSE. + optional string true_value = 2; + optional string false_value = 3; +} + +// BEGIN SEMANTIC-TYPES-PROTOS +// Semantic domains are specialized feature domains. For example a string +// Feature might represent a Time of a specific format. +// Semantic domains are defined as protocol buffers to allow further sub-types / +// specialization, e.g: NaturalLanguageDomain can provide information on the +// language of the text. + +// Natural language text. +message NaturalLanguageDomain {} + +// Image data. +message ImageDomain {} + +// Knowledge graph ID, see: https://www.wikidata.org/wiki/Property:P646 +message MIDDomain {} + +// A URL, see: https://en.wikipedia.org/wiki/URL +message URLDomain {} + +// Time or date representation. +message TimeDomain { + enum IntegerTimeFormat { + FORMAT_UNKNOWN = 0; + UNIX_DAYS = 5; // Number of days since 1970-01-01. + UNIX_SECONDS = 1; + UNIX_MILLISECONDS = 2; + UNIX_MICROSECONDS = 3; + UNIX_NANOSECONDS = 4; + } + + oneof format { + // Expected format that contains a combination of regular characters and + // special format specifiers. Format specifiers are a subset of the + // strptime standard. + string string_format = 1; + + // Expected format of integer times. + IntegerTimeFormat integer_format = 2; + } +} + +// Time of day, without a particular date. +message TimeOfDayDomain { + enum IntegerTimeOfDayFormat { + FORMAT_UNKNOWN = 0; + // Time values, containing hour/minute/second/nanos, encoded into 8-byte + // bit fields following the ZetaSQL convention: + // 6 5 4 3 2 1 + // MSB 3210987654321098765432109876543210987654321098765432109876543210 LSB + // | H || M || S ||---------- nanos -----------| + PACKED_64_NANOS = 1; + } + + oneof format { + // Expected format that contains a combination of regular characters and + // special format specifiers. Format specifiers are a subset of the + // strptime standard. + string string_format = 1; + + // Expected format of integer times. + IntegerTimeOfDayFormat integer_format = 2; + } +} +// END SEMANTIC-TYPES-PROTOS + +// Describes the physical representation of a feature. +// It may be different than the logical representation, which +// is represented as a Domain. +enum FeatureType { + TYPE_UNKNOWN = 0; + BYTES = 1; + INT = 2; + FLOAT = 3; + STRUCT = 4; +} + +// Describes constraints on the presence of the feature in the data. +message FeaturePresence { + // Minimum fraction of examples that have this feature. + optional double min_fraction = 1; + // Minimum number of examples that have this feature. + optional int64 min_count = 2; +} + +// Records constraints on the presence of a feature inside a "group" context +// (e.g., .presence inside a group of features that define a sequence). +message FeaturePresenceWithinGroup { + optional bool required = 1; +} + +// Checks that the L-infinity norm is below a certain threshold between the +// two discrete distributions. Since this is applied to a FeatureNameStatistics, +// it only considers the top k. +// L_infty(p,q) = max_i |p_i-q_i| +message InfinityNorm { + // The InfinityNorm is in the interval [0.0, 1.0] so sensible bounds should + // be in the interval [0.0, 1.0). + optional double threshold = 1; +} + +message FeatureComparator { + optional InfinityNorm infinity_norm = 1; +} + +// A TensorRepresentation captures the intent for converting columns in a +// dataset to TensorFlow Tensors (or more generally, tf.CompositeTensors). +// Note that one tf.CompositeTensor may consist of data from multiple columns, +// for example, a N-dimensional tf.SparseTensor may need N + 1 columns to +// provide the sparse indices and values. +// Note that the "column name" that a TensorRepresentation needs is a +// string, not a Path -- it means that the column name identifies a top-level +// Feature in the schema (i.e. you cannot specify a Feature nested in a STRUCT +// Feature). +message TensorRepresentation { + message DefaultValue { + oneof kind { + double float_value = 1; + // Note that the data column might be of a shorter integral type. It's the + // user's responsitiblity to make sure the default value fits that type. + int64 int_value = 2; + bytes bytes_value = 3; + // uint_value should only be used if the default value can't fit in a + // int64 (`int_value`). + uint64 uint_value = 4; + } + } + + // A tf.Tensor + message DenseTensor { + // Identifies the column in the dataset that provides the values of this + // Tensor. + optional string column_name = 1; + // The shape of each row of the data (i.e. does not include the batch + // dimension) + optional FixedShape shape = 2; + // If this column is missing values in a row, the default_value will be + // used to fill that row. + optional DefaultValue default_value = 3; + } + + // A ragged tf.SparseTensor that models nested lists. + message VarLenSparseTensor { + // Identifies the column in the dataset that should be converted to the + // VarLenSparseTensor. + optional string column_name = 1; + } + + // A tf.SparseTensor whose indices and values come from separate data columns. + // This will replace Schema.sparse_feature eventually. + // The index columns must be of INT type, and all the columns must co-occur + // and have the same valency at the same row. + message SparseTensor { + // The dense shape of the resulting SparseTensor (does not include the batch + // dimension). + optional FixedShape dense_shape = 1; + // The columns constitute the coordinates of the values. + // indices_column[i][j] contains the coordinate of the i-th dimension of the + // j-th value. + repeated string index_column_names = 2; + // The column that contains the values. + optional string value_column_name = 3; + } + + oneof kind { + DenseTensor dense_tensor = 1; + VarLenSparseTensor varlen_sparse_tensor = 2; + SparseTensor sparse_tensor = 3; + } +} + +// A TensorRepresentationGroup is a collection of TensorRepresentations with +// names. These names may serve as identifiers when converting the dataset +// to a collection of Tensors or tf.CompositeTensors. +// For example, given the following group: +// { +// key: "dense_tensor" +// tensor_representation { +// dense_tensor { +// column_name: "univalent_feature" +// shape { +// dim { +// size: 1 +// } +// } +// default_value { +// float_value: 0 +// } +// } +// } +// } +// { +// key: "varlen_sparse_tensor" +// tensor_representation { +// varlen_sparse_tensor { +// column_name: "multivalent_feature" +// } +// } +// } +// +// Then the schema is expected to have feature "univalent_feature" and +// "multivalent_feature", and when a batch of data is converted to Tensors using +// this TensorRepresentationGroup, the result may be the following dict: +// { +// "dense_tensor": tf.Tensor(...), +// "varlen_sparse_tensor": tf.SparseTensor(...), +// } +message TensorRepresentationGroup { + map tensor_representation = 1; +} diff --git a/sdk/go/README.md b/sdk/go/README.md index 6084f909931..79211df4a51 100644 --- a/sdk/go/README.md +++ b/sdk/go/README.md @@ -6,7 +6,7 @@ The Feast golang SDK currently only supports retrieval from online stores. ```{go} import ( "context" - feast "github.com/gojek/feast/sdk/go" + feast "github.com/feast-dev/feast/sdk/go" ) func main() { @@ -17,7 +17,7 @@ func main() { ctx := context.Background() req := feast.OnlineFeaturesRequest{ - Features: []string{"my_project_1/feature1:1", "my_project_2/feature1:1", "my_project_4/feature3", "feature2:2", "feature2"}, + Features: []string{"my_project_1/feature1", "my_project_2/feature1", "my_project_4/feature3", "feature2", "feature2"}, Entities: []feast.Row{ {"entity1": feast.Int64Val(1), "entity2": feast.StrVal("bob")}, {"entity1": feast.Int64Val(1), "entity2": feast.StrVal("annie")}, @@ -40,10 +40,10 @@ func main() { If all features retrieved are of a single type, Feast provides convenience functions to retrieve your features as a vector of feature values: ```{go} arr, err := resp.Int64Arrays( - []string{"my_project_1/feature1:1", - "my_project_2/feature1:1", + []string{"my_project_1/feature1", + "my_project_2/feature1", "my_project_4/feature3", - "feature2:2", + "feature2", "feature2"}, // order of features []int64{1,2,3,4,5}) // fillNa values ``` diff --git a/sdk/go/client.go b/sdk/go/client.go index 0eaf89b04a2..38c9e2fb7e5 100644 --- a/sdk/go/client.go +++ b/sdk/go/client.go @@ -5,7 +5,8 @@ import ( "fmt" "github.com/opentracing/opentracing-go" - "github.com/gojek/feast/sdk/go/protos/feast/serving" + "github.com/feast-dev/feast/sdk/go/protos/feast/serving" + "github.com/feast-dev/feast/sdk/go/protos/feast/types" "google.golang.org/grpc" "go.opencensus.io/plugin/ocgrpc" @@ -50,10 +51,35 @@ func (fc *GrpcClient) GetOnlineFeatures(ctx context.Context, req *OnlineFeatures } resp, err := fc.cli.GetOnlineFeatures(ctx, featuresRequest) + // collect unqiue entity refs from entity rows + var entityRefs map[string]struct{} + for _, entityRows := range req.Entities { + for ref, _ := range entityRows { + entityRefs[ref] = struct{}{} + } + } + + // strip projects from to projects + for _, fieldValue := range resp.GetFieldValues() { + var stripFields map[string]*types.Value + for refStr, value := range fieldValue.Fields { + _, isEntity := entityRefs[refStr] + if !isEntity { // is feature ref + featureRef, err := parseFeatureRef(refStr, true) + if err != nil { + return nil, err + } + stripRefStr := toFeatureRefStr(featureRef) + stripFields[stripRefStr] = value + } + } + fieldValue.Fields = stripFields + } + return &OnlineFeaturesResponse{RawResponse: resp}, err } -// GetInfo gets information about the feast serving instance this client is connected to. +// GetFeastServingInfo gets information about the feast serving instance this client is connected to. func (fc *GrpcClient) GetFeastServingInfo(ctx context.Context, in *serving.GetFeastServingInfoRequest) ( *serving.GetFeastServingInfoResponse, error) { span, ctx := opentracing.StartSpanFromContext(ctx, "get_info") @@ -62,7 +88,7 @@ func (fc *GrpcClient) GetFeastServingInfo(ctx context.Context, in *serving.GetFe return fc.cli.GetFeastServingInfo(ctx, in) } -// Closes the grpc connection. +// Close the grpc connection. func (fc *GrpcClient) Close() error { return fc.conn.Close() -} \ No newline at end of file +} diff --git a/sdk/go/go.mod b/sdk/go/go.mod index 0def759a4f2..06bd6936162 100644 --- a/sdk/go/go.mod +++ b/sdk/go/go.mod @@ -1,12 +1,13 @@ -module github.com/gojek/feast/sdk/go +module github.com/feast-dev/feast/sdk/go go 1.13 require ( - github.com/golang/protobuf v1.3.2 - github.com/google/go-cmp v0.3.1 + github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0 + github.com/google/go-cmp v0.4.0 github.com/opentracing/opentracing-go v1.1.0 github.com/stretchr/testify v1.4.0 // indirect go.opencensus.io v0.22.1 - google.golang.org/grpc v1.24.0 + google.golang.org/grpc v1.28.0 + google.golang.org/protobuf v1.21.0 ) diff --git a/sdk/go/go.sum b/sdk/go/go.sum index 04cf3d8d7f3..f08f3026170 100644 --- a/sdk/go/go.sum +++ b/sdk/go/go.sum @@ -1,8 +1,13 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= @@ -12,16 +17,26 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0 h1:aRz0NBceriICVtjhCgKkDvl+RudKu1CT6h0ZvUTrNfE= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -57,16 +72,28 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb h1:i1Ppqkc3WQXikh8bXiwHqAN5Rv3/qDCcRk0/Otx73BY= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.24.0 h1:vb/1TCsVn3DcJlQ0Gs1yB1pKI6Do2/QNwxdKqmc/b0s= -google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.28.0 h1:bO/TA4OxCOummhSf10siHuG7vJOiwh7SpRpFZDkOgl4= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= diff --git a/sdk/go/protos/feast/core/CoreService.pb.go b/sdk/go/protos/feast/core/CoreService.pb.go index 45ad9ed79a4..75f988ed528 100644 --- a/sdk/go/protos/feast/core/CoreService.pb.go +++ b/sdk/go/protos/feast/core/CoreService.pb.go @@ -1,58 +1,105 @@ +// +// Copyright 2018 The Feast Authors +// +// 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.23.0 +// protoc v3.10.0 // source: feast/core/CoreService.proto package core import ( context "context" - fmt "fmt" proto "github.com/golang/protobuf/proto" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" - math "math" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" ) -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf +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 proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 +// TODO: 0 should correspond to invalid rather than NO_CHANGE type ApplyFeatureSetResponse_Status int32 const ( - // Latest feature set version is consistent with provided feature set + // Latest feature set is consistent with provided feature set ApplyFeatureSetResponse_NO_CHANGE ApplyFeatureSetResponse_Status = 0 - // New feature set or feature set version created + // New feature set created ApplyFeatureSetResponse_CREATED ApplyFeatureSetResponse_Status = 1 // Error occurred while trying to apply changes ApplyFeatureSetResponse_ERROR ApplyFeatureSetResponse_Status = 2 + // Changes detected and updated successfully + ApplyFeatureSetResponse_UPDATED ApplyFeatureSetResponse_Status = 3 ) -var ApplyFeatureSetResponse_Status_name = map[int32]string{ - 0: "NO_CHANGE", - 1: "CREATED", - 2: "ERROR", -} +// Enum value maps for ApplyFeatureSetResponse_Status. +var ( + ApplyFeatureSetResponse_Status_name = map[int32]string{ + 0: "NO_CHANGE", + 1: "CREATED", + 2: "ERROR", + 3: "UPDATED", + } + ApplyFeatureSetResponse_Status_value = map[string]int32{ + "NO_CHANGE": 0, + "CREATED": 1, + "ERROR": 2, + "UPDATED": 3, + } +) -var ApplyFeatureSetResponse_Status_value = map[string]int32{ - "NO_CHANGE": 0, - "CREATED": 1, - "ERROR": 2, +func (x ApplyFeatureSetResponse_Status) Enum() *ApplyFeatureSetResponse_Status { + p := new(ApplyFeatureSetResponse_Status) + *p = x + return p } func (x ApplyFeatureSetResponse_Status) String() string { - return proto.EnumName(ApplyFeatureSetResponse_Status_name, int32(x)) + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ApplyFeatureSetResponse_Status) Descriptor() protoreflect.EnumDescriptor { + return file_feast_core_CoreService_proto_enumTypes[0].Descriptor() +} + +func (ApplyFeatureSetResponse_Status) Type() protoreflect.EnumType { + return &file_feast_core_CoreService_proto_enumTypes[0] +} + +func (x ApplyFeatureSetResponse_Status) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) } +// Deprecated: Use ApplyFeatureSetResponse_Status.Descriptor instead. func (ApplyFeatureSetResponse_Status) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_d9be266444105411, []int{7, 0} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{7, 0} } type UpdateStoreResponse_Status int32 @@ -64,943 +111,2017 @@ const ( UpdateStoreResponse_UPDATED UpdateStoreResponse_Status = 1 ) -var UpdateStoreResponse_Status_name = map[int32]string{ - 0: "NO_CHANGE", - 1: "UPDATED", -} +// Enum value maps for UpdateStoreResponse_Status. +var ( + UpdateStoreResponse_Status_name = map[int32]string{ + 0: "NO_CHANGE", + 1: "UPDATED", + } + UpdateStoreResponse_Status_value = map[string]int32{ + "NO_CHANGE": 0, + "UPDATED": 1, + } +) -var UpdateStoreResponse_Status_value = map[string]int32{ - "NO_CHANGE": 0, - "UPDATED": 1, +func (x UpdateStoreResponse_Status) Enum() *UpdateStoreResponse_Status { + p := new(UpdateStoreResponse_Status) + *p = x + return p } func (x UpdateStoreResponse_Status) String() string { - return proto.EnumName(UpdateStoreResponse_Status_name, int32(x)) + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (UpdateStoreResponse_Status) Descriptor() protoreflect.EnumDescriptor { + return file_feast_core_CoreService_proto_enumTypes[1].Descriptor() +} + +func (UpdateStoreResponse_Status) Type() protoreflect.EnumType { + return &file_feast_core_CoreService_proto_enumTypes[1] +} + +func (x UpdateStoreResponse_Status) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) } +// Deprecated: Use UpdateStoreResponse_Status.Descriptor instead. func (UpdateStoreResponse_Status) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_d9be266444105411, []int{11, 0} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{11, 0} } // Request for a single feature set type GetFeatureSetRequest struct { - // Name of project the feature set belongs to (required) + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Name of project the feature set belongs to. If omitted will default to 'default' project. Project string `protobuf:"bytes,3,opt,name=project,proto3" json:"project,omitempty"` // Name of feature set (required). Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - // Version of feature set (optional). If omitted then latest feature set will be returned. - Version int32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` } -func (m *GetFeatureSetRequest) Reset() { *m = GetFeatureSetRequest{} } -func (m *GetFeatureSetRequest) String() string { return proto.CompactTextString(m) } -func (*GetFeatureSetRequest) ProtoMessage() {} -func (*GetFeatureSetRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_d9be266444105411, []int{0} +func (x *GetFeatureSetRequest) Reset() { + *x = GetFeatureSetRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *GetFeatureSetRequest) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_GetFeatureSetRequest.Unmarshal(m, b) -} -func (m *GetFeatureSetRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_GetFeatureSetRequest.Marshal(b, m, deterministic) -} -func (m *GetFeatureSetRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_GetFeatureSetRequest.Merge(m, src) -} -func (m *GetFeatureSetRequest) XXX_Size() int { - return xxx_messageInfo_GetFeatureSetRequest.Size(m) -} -func (m *GetFeatureSetRequest) XXX_DiscardUnknown() { - xxx_messageInfo_GetFeatureSetRequest.DiscardUnknown(m) +func (x *GetFeatureSetRequest) String() string { + return protoimpl.X.MessageStringOf(x) } -var xxx_messageInfo_GetFeatureSetRequest proto.InternalMessageInfo +func (*GetFeatureSetRequest) ProtoMessage() {} -func (m *GetFeatureSetRequest) GetProject() string { - if m != nil { - return m.Project +func (x *GetFeatureSetRequest) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return "" + return mi.MessageOf(x) } -func (m *GetFeatureSetRequest) GetName() string { - if m != nil { - return m.Name +// Deprecated: Use GetFeatureSetRequest.ProtoReflect.Descriptor instead. +func (*GetFeatureSetRequest) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{0} +} + +func (x *GetFeatureSetRequest) GetProject() string { + if x != nil { + return x.Project } return "" } -func (m *GetFeatureSetRequest) GetVersion() int32 { - if m != nil { - return m.Version +func (x *GetFeatureSetRequest) GetName() string { + if x != nil { + return x.Name } - return 0 + return "" } // Response containing a single feature set type GetFeatureSetResponse struct { - FeatureSet *FeatureSet `protobuf:"bytes,1,opt,name=feature_set,json=featureSet,proto3" json:"feature_set,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields -func (m *GetFeatureSetResponse) Reset() { *m = GetFeatureSetResponse{} } -func (m *GetFeatureSetResponse) String() string { return proto.CompactTextString(m) } -func (*GetFeatureSetResponse) ProtoMessage() {} -func (*GetFeatureSetResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_d9be266444105411, []int{1} + FeatureSet *FeatureSet `protobuf:"bytes,1,opt,name=feature_set,json=featureSet,proto3" json:"feature_set,omitempty"` } -func (m *GetFeatureSetResponse) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_GetFeatureSetResponse.Unmarshal(m, b) -} -func (m *GetFeatureSetResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_GetFeatureSetResponse.Marshal(b, m, deterministic) -} -func (m *GetFeatureSetResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_GetFeatureSetResponse.Merge(m, src) +func (x *GetFeatureSetResponse) Reset() { + *x = GetFeatureSetResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *GetFeatureSetResponse) XXX_Size() int { - return xxx_messageInfo_GetFeatureSetResponse.Size(m) + +func (x *GetFeatureSetResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *GetFeatureSetResponse) XXX_DiscardUnknown() { - xxx_messageInfo_GetFeatureSetResponse.DiscardUnknown(m) + +func (*GetFeatureSetResponse) ProtoMessage() {} + +func (x *GetFeatureSetResponse) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_GetFeatureSetResponse proto.InternalMessageInfo +// Deprecated: Use GetFeatureSetResponse.ProtoReflect.Descriptor instead. +func (*GetFeatureSetResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{1} +} -func (m *GetFeatureSetResponse) GetFeatureSet() *FeatureSet { - if m != nil { - return m.FeatureSet +func (x *GetFeatureSetResponse) GetFeatureSet() *FeatureSet { + if x != nil { + return x.FeatureSet } return nil } // Retrieves details for all versions of a specific feature set type ListFeatureSetsRequest struct { - Filter *ListFeatureSetsRequest_Filter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields -func (m *ListFeatureSetsRequest) Reset() { *m = ListFeatureSetsRequest{} } -func (m *ListFeatureSetsRequest) String() string { return proto.CompactTextString(m) } -func (*ListFeatureSetsRequest) ProtoMessage() {} -func (*ListFeatureSetsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_d9be266444105411, []int{2} + Filter *ListFeatureSetsRequest_Filter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` } -func (m *ListFeatureSetsRequest) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_ListFeatureSetsRequest.Unmarshal(m, b) -} -func (m *ListFeatureSetsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_ListFeatureSetsRequest.Marshal(b, m, deterministic) -} -func (m *ListFeatureSetsRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_ListFeatureSetsRequest.Merge(m, src) -} -func (m *ListFeatureSetsRequest) XXX_Size() int { - return xxx_messageInfo_ListFeatureSetsRequest.Size(m) +func (x *ListFeatureSetsRequest) Reset() { + *x = ListFeatureSetsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *ListFeatureSetsRequest) XXX_DiscardUnknown() { - xxx_messageInfo_ListFeatureSetsRequest.DiscardUnknown(m) + +func (x *ListFeatureSetsRequest) String() string { + return protoimpl.X.MessageStringOf(x) } -var xxx_messageInfo_ListFeatureSetsRequest proto.InternalMessageInfo +func (*ListFeatureSetsRequest) ProtoMessage() {} -func (m *ListFeatureSetsRequest) GetFilter() *ListFeatureSetsRequest_Filter { - if m != nil { - return m.Filter +func (x *ListFeatureSetsRequest) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return nil + return mi.MessageOf(x) } -type ListFeatureSetsRequest_Filter struct { - // Name of project that the feature sets belongs to. This can be one of - // - [project_name] - // - * - // If an asterisk is provided, filtering on projects will be disabled. All projects will - // be matched. It is NOT possible to provide an asterisk with a string in order to do - // pattern matching. - Project string `protobuf:"bytes,3,opt,name=project,proto3" json:"project,omitempty"` - // Name of the desired feature set. Asterisks can be used as wildcards in the name. - // Matching on names is only permitted if a specific project is defined. It is disallowed - // If the project name is set to "*" - // e.g. - // - * can be used to match all feature sets - // - my-feature-set* can be used to match all features prefixed by "my-feature-set" - // - my-feature-set-6 can be used to select a single feature set - FeatureSetName string `protobuf:"bytes,1,opt,name=feature_set_name,json=featureSetName,proto3" json:"feature_set_name,omitempty"` - // Versions of the given feature sets that will be returned. - // Valid options for version: - // "latest": only the latest version is returned. - // "*": Subscribe to all versions - // [version number]: pin to a specific version. Project and feature set name must be - // explicitly defined if a specific version is pinned. - FeatureSetVersion string `protobuf:"bytes,2,opt,name=feature_set_version,json=featureSetVersion,proto3" json:"feature_set_version,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *ListFeatureSetsRequest_Filter) Reset() { *m = ListFeatureSetsRequest_Filter{} } -func (m *ListFeatureSetsRequest_Filter) String() string { return proto.CompactTextString(m) } -func (*ListFeatureSetsRequest_Filter) ProtoMessage() {} -func (*ListFeatureSetsRequest_Filter) Descriptor() ([]byte, []int) { - return fileDescriptor_d9be266444105411, []int{2, 0} +// Deprecated: Use ListFeatureSetsRequest.ProtoReflect.Descriptor instead. +func (*ListFeatureSetsRequest) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{2} } -func (m *ListFeatureSetsRequest_Filter) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_ListFeatureSetsRequest_Filter.Unmarshal(m, b) -} -func (m *ListFeatureSetsRequest_Filter) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_ListFeatureSetsRequest_Filter.Marshal(b, m, deterministic) +func (x *ListFeatureSetsRequest) GetFilter() *ListFeatureSetsRequest_Filter { + if x != nil { + return x.Filter + } + return nil } -func (m *ListFeatureSetsRequest_Filter) XXX_Merge(src proto.Message) { - xxx_messageInfo_ListFeatureSetsRequest_Filter.Merge(m, src) + +type ListFeatureSetsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + FeatureSets []*FeatureSet `protobuf:"bytes,1,rep,name=feature_sets,json=featureSets,proto3" json:"feature_sets,omitempty"` } -func (m *ListFeatureSetsRequest_Filter) XXX_Size() int { - return xxx_messageInfo_ListFeatureSetsRequest_Filter.Size(m) + +func (x *ListFeatureSetsResponse) Reset() { + *x = ListFeatureSetsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *ListFeatureSetsRequest_Filter) XXX_DiscardUnknown() { - xxx_messageInfo_ListFeatureSetsRequest_Filter.DiscardUnknown(m) + +func (x *ListFeatureSetsResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -var xxx_messageInfo_ListFeatureSetsRequest_Filter proto.InternalMessageInfo +func (*ListFeatureSetsResponse) ProtoMessage() {} -func (m *ListFeatureSetsRequest_Filter) GetProject() string { - if m != nil { - return m.Project +func (x *ListFeatureSetsResponse) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return "" + return mi.MessageOf(x) +} + +// Deprecated: Use ListFeatureSetsResponse.ProtoReflect.Descriptor instead. +func (*ListFeatureSetsResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{3} } -func (m *ListFeatureSetsRequest_Filter) GetFeatureSetName() string { - if m != nil { - return m.FeatureSetName +func (x *ListFeatureSetsResponse) GetFeatureSets() []*FeatureSet { + if x != nil { + return x.FeatureSets } - return "" + return nil } -func (m *ListFeatureSetsRequest_Filter) GetFeatureSetVersion() string { - if m != nil { - return m.FeatureSetVersion +type ListStoresRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Filter *ListStoresRequest_Filter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` +} + +func (x *ListStoresRequest) Reset() { + *x = ListStoresRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } - return "" } -type ListFeatureSetsResponse struct { - FeatureSets []*FeatureSet `protobuf:"bytes,1,rep,name=feature_sets,json=featureSets,proto3" json:"feature_sets,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` +func (x *ListStoresRequest) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *ListFeatureSetsResponse) Reset() { *m = ListFeatureSetsResponse{} } -func (m *ListFeatureSetsResponse) String() string { return proto.CompactTextString(m) } -func (*ListFeatureSetsResponse) ProtoMessage() {} -func (*ListFeatureSetsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_d9be266444105411, []int{3} +func (*ListStoresRequest) ProtoMessage() {} + +func (x *ListStoresRequest) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -func (m *ListFeatureSetsResponse) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_ListFeatureSetsResponse.Unmarshal(m, b) +// Deprecated: Use ListStoresRequest.ProtoReflect.Descriptor instead. +func (*ListStoresRequest) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{4} } -func (m *ListFeatureSetsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_ListFeatureSetsResponse.Marshal(b, m, deterministic) + +func (x *ListStoresRequest) GetFilter() *ListStoresRequest_Filter { + if x != nil { + return x.Filter + } + return nil } -func (m *ListFeatureSetsResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_ListFeatureSetsResponse.Merge(m, src) + +type ListStoresResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Store []*Store `protobuf:"bytes,1,rep,name=store,proto3" json:"store,omitempty"` } -func (m *ListFeatureSetsResponse) XXX_Size() int { - return xxx_messageInfo_ListFeatureSetsResponse.Size(m) + +func (x *ListStoresResponse) Reset() { + *x = ListStoresResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *ListFeatureSetsResponse) XXX_DiscardUnknown() { - xxx_messageInfo_ListFeatureSetsResponse.DiscardUnknown(m) + +func (x *ListStoresResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -var xxx_messageInfo_ListFeatureSetsResponse proto.InternalMessageInfo +func (*ListStoresResponse) ProtoMessage() {} -func (m *ListFeatureSetsResponse) GetFeatureSets() []*FeatureSet { - if m != nil { - return m.FeatureSets +func (x *ListStoresResponse) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return nil + return mi.MessageOf(x) } -type ListStoresRequest struct { - Filter *ListStoresRequest_Filter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` +// Deprecated: Use ListStoresResponse.ProtoReflect.Descriptor instead. +func (*ListStoresResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{5} } -func (m *ListStoresRequest) Reset() { *m = ListStoresRequest{} } -func (m *ListStoresRequest) String() string { return proto.CompactTextString(m) } -func (*ListStoresRequest) ProtoMessage() {} -func (*ListStoresRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_d9be266444105411, []int{4} +func (x *ListStoresResponse) GetStore() []*Store { + if x != nil { + return x.Store + } + return nil } -func (m *ListStoresRequest) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_ListStoresRequest.Unmarshal(m, b) -} -func (m *ListStoresRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_ListStoresRequest.Marshal(b, m, deterministic) +type ApplyFeatureSetRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Feature set version + // If project is unspecified, will default to 'default' project. + // If project specified does not exist, the project would be automatically created. + FeatureSet *FeatureSet `protobuf:"bytes,1,opt,name=feature_set,json=featureSet,proto3" json:"feature_set,omitempty"` } -func (m *ListStoresRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_ListStoresRequest.Merge(m, src) + +func (x *ApplyFeatureSetRequest) Reset() { + *x = ApplyFeatureSetRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *ListStoresRequest) XXX_Size() int { - return xxx_messageInfo_ListStoresRequest.Size(m) + +func (x *ApplyFeatureSetRequest) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *ListStoresRequest) XXX_DiscardUnknown() { - xxx_messageInfo_ListStoresRequest.DiscardUnknown(m) + +func (*ApplyFeatureSetRequest) ProtoMessage() {} + +func (x *ApplyFeatureSetRequest) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_ListStoresRequest proto.InternalMessageInfo +// Deprecated: Use ApplyFeatureSetRequest.ProtoReflect.Descriptor instead. +func (*ApplyFeatureSetRequest) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{6} +} -func (m *ListStoresRequest) GetFilter() *ListStoresRequest_Filter { - if m != nil { - return m.Filter +func (x *ApplyFeatureSetRequest) GetFeatureSet() *FeatureSet { + if x != nil { + return x.FeatureSet } return nil } -type ListStoresRequest_Filter struct { - // Name of desired store. Regex is not supported in this query. - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` +type ApplyFeatureSetResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + FeatureSet *FeatureSet `protobuf:"bytes,1,opt,name=feature_set,json=featureSet,proto3" json:"feature_set,omitempty"` + Status ApplyFeatureSetResponse_Status `protobuf:"varint,2,opt,name=status,proto3,enum=feast.core.ApplyFeatureSetResponse_Status" json:"status,omitempty"` } -func (m *ListStoresRequest_Filter) Reset() { *m = ListStoresRequest_Filter{} } -func (m *ListStoresRequest_Filter) String() string { return proto.CompactTextString(m) } -func (*ListStoresRequest_Filter) ProtoMessage() {} -func (*ListStoresRequest_Filter) Descriptor() ([]byte, []int) { - return fileDescriptor_d9be266444105411, []int{4, 0} +func (x *ApplyFeatureSetResponse) Reset() { + *x = ApplyFeatureSetResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *ListStoresRequest_Filter) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_ListStoresRequest_Filter.Unmarshal(m, b) +func (x *ApplyFeatureSetResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *ListStoresRequest_Filter) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_ListStoresRequest_Filter.Marshal(b, m, deterministic) + +func (*ApplyFeatureSetResponse) ProtoMessage() {} + +func (x *ApplyFeatureSetResponse) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -func (m *ListStoresRequest_Filter) XXX_Merge(src proto.Message) { - xxx_messageInfo_ListStoresRequest_Filter.Merge(m, src) + +// Deprecated: Use ApplyFeatureSetResponse.ProtoReflect.Descriptor instead. +func (*ApplyFeatureSetResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{7} } -func (m *ListStoresRequest_Filter) XXX_Size() int { - return xxx_messageInfo_ListStoresRequest_Filter.Size(m) + +func (x *ApplyFeatureSetResponse) GetFeatureSet() *FeatureSet { + if x != nil { + return x.FeatureSet + } + return nil } -func (m *ListStoresRequest_Filter) XXX_DiscardUnknown() { - xxx_messageInfo_ListStoresRequest_Filter.DiscardUnknown(m) + +func (x *ApplyFeatureSetResponse) GetStatus() ApplyFeatureSetResponse_Status { + if x != nil { + return x.Status + } + return ApplyFeatureSetResponse_NO_CHANGE } -var xxx_messageInfo_ListStoresRequest_Filter proto.InternalMessageInfo +type GetFeastCoreVersionRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} -func (m *ListStoresRequest_Filter) GetName() string { - if m != nil { - return m.Name +func (x *GetFeastCoreVersionRequest) Reset() { + *x = GetFeastCoreVersionRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } - return "" } -type ListStoresResponse struct { - Store []*Store `protobuf:"bytes,1,rep,name=store,proto3" json:"store,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` +func (x *GetFeastCoreVersionRequest) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *ListStoresResponse) Reset() { *m = ListStoresResponse{} } -func (m *ListStoresResponse) String() string { return proto.CompactTextString(m) } -func (*ListStoresResponse) ProtoMessage() {} -func (*ListStoresResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_d9be266444105411, []int{5} -} +func (*GetFeastCoreVersionRequest) ProtoMessage() {} -func (m *ListStoresResponse) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_ListStoresResponse.Unmarshal(m, b) +func (x *GetFeastCoreVersionRequest) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -func (m *ListStoresResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_ListStoresResponse.Marshal(b, m, deterministic) + +// Deprecated: Use GetFeastCoreVersionRequest.ProtoReflect.Descriptor instead. +func (*GetFeastCoreVersionRequest) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{8} } -func (m *ListStoresResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_ListStoresResponse.Merge(m, src) + +type GetFeastCoreVersionResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` } -func (m *ListStoresResponse) XXX_Size() int { - return xxx_messageInfo_ListStoresResponse.Size(m) + +func (x *GetFeastCoreVersionResponse) Reset() { + *x = GetFeastCoreVersionResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *ListStoresResponse) XXX_DiscardUnknown() { - xxx_messageInfo_ListStoresResponse.DiscardUnknown(m) + +func (x *GetFeastCoreVersionResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -var xxx_messageInfo_ListStoresResponse proto.InternalMessageInfo +func (*GetFeastCoreVersionResponse) ProtoMessage() {} -func (m *ListStoresResponse) GetStore() []*Store { - if m != nil { - return m.Store +func (x *GetFeastCoreVersionResponse) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return nil + return mi.MessageOf(x) } -type ApplyFeatureSetRequest struct { - // Feature set version and source will be ignored - FeatureSet *FeatureSet `protobuf:"bytes,1,opt,name=feature_set,json=featureSet,proto3" json:"feature_set,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` +// Deprecated: Use GetFeastCoreVersionResponse.ProtoReflect.Descriptor instead. +func (*GetFeastCoreVersionResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{9} } -func (m *ApplyFeatureSetRequest) Reset() { *m = ApplyFeatureSetRequest{} } -func (m *ApplyFeatureSetRequest) String() string { return proto.CompactTextString(m) } -func (*ApplyFeatureSetRequest) ProtoMessage() {} -func (*ApplyFeatureSetRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_d9be266444105411, []int{6} +func (x *GetFeastCoreVersionResponse) GetVersion() string { + if x != nil { + return x.Version + } + return "" } -func (m *ApplyFeatureSetRequest) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_ApplyFeatureSetRequest.Unmarshal(m, b) -} -func (m *ApplyFeatureSetRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_ApplyFeatureSetRequest.Marshal(b, m, deterministic) -} -func (m *ApplyFeatureSetRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_ApplyFeatureSetRequest.Merge(m, src) +type UpdateStoreRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Store *Store `protobuf:"bytes,1,opt,name=store,proto3" json:"store,omitempty"` } -func (m *ApplyFeatureSetRequest) XXX_Size() int { - return xxx_messageInfo_ApplyFeatureSetRequest.Size(m) + +func (x *UpdateStoreRequest) Reset() { + *x = UpdateStoreRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *ApplyFeatureSetRequest) XXX_DiscardUnknown() { - xxx_messageInfo_ApplyFeatureSetRequest.DiscardUnknown(m) + +func (x *UpdateStoreRequest) String() string { + return protoimpl.X.MessageStringOf(x) } -var xxx_messageInfo_ApplyFeatureSetRequest proto.InternalMessageInfo +func (*UpdateStoreRequest) ProtoMessage() {} -func (m *ApplyFeatureSetRequest) GetFeatureSet() *FeatureSet { - if m != nil { - return m.FeatureSet +func (x *UpdateStoreRequest) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return nil + return mi.MessageOf(x) } -type ApplyFeatureSetResponse struct { - // Feature set response has been enriched with version and source information - FeatureSet *FeatureSet `protobuf:"bytes,1,opt,name=feature_set,json=featureSet,proto3" json:"feature_set,omitempty"` - Status ApplyFeatureSetResponse_Status `protobuf:"varint,2,opt,name=status,proto3,enum=feast.core.ApplyFeatureSetResponse_Status" json:"status,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` +// Deprecated: Use UpdateStoreRequest.ProtoReflect.Descriptor instead. +func (*UpdateStoreRequest) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{10} } -func (m *ApplyFeatureSetResponse) Reset() { *m = ApplyFeatureSetResponse{} } -func (m *ApplyFeatureSetResponse) String() string { return proto.CompactTextString(m) } -func (*ApplyFeatureSetResponse) ProtoMessage() {} -func (*ApplyFeatureSetResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_d9be266444105411, []int{7} +func (x *UpdateStoreRequest) GetStore() *Store { + if x != nil { + return x.Store + } + return nil } -func (m *ApplyFeatureSetResponse) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_ApplyFeatureSetResponse.Unmarshal(m, b) -} -func (m *ApplyFeatureSetResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_ApplyFeatureSetResponse.Marshal(b, m, deterministic) +type UpdateStoreResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Store *Store `protobuf:"bytes,1,opt,name=store,proto3" json:"store,omitempty"` + Status UpdateStoreResponse_Status `protobuf:"varint,2,opt,name=status,proto3,enum=feast.core.UpdateStoreResponse_Status" json:"status,omitempty"` } -func (m *ApplyFeatureSetResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_ApplyFeatureSetResponse.Merge(m, src) + +func (x *UpdateStoreResponse) Reset() { + *x = UpdateStoreResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *ApplyFeatureSetResponse) XXX_Size() int { - return xxx_messageInfo_ApplyFeatureSetResponse.Size(m) + +func (x *UpdateStoreResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *ApplyFeatureSetResponse) XXX_DiscardUnknown() { - xxx_messageInfo_ApplyFeatureSetResponse.DiscardUnknown(m) + +func (*UpdateStoreResponse) ProtoMessage() {} + +func (x *UpdateStoreResponse) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_ApplyFeatureSetResponse proto.InternalMessageInfo +// Deprecated: Use UpdateStoreResponse.ProtoReflect.Descriptor instead. +func (*UpdateStoreResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{11} +} -func (m *ApplyFeatureSetResponse) GetFeatureSet() *FeatureSet { - if m != nil { - return m.FeatureSet +func (x *UpdateStoreResponse) GetStore() *Store { + if x != nil { + return x.Store } return nil } -func (m *ApplyFeatureSetResponse) GetStatus() ApplyFeatureSetResponse_Status { - if m != nil { - return m.Status +func (x *UpdateStoreResponse) GetStatus() UpdateStoreResponse_Status { + if x != nil { + return x.Status } - return ApplyFeatureSetResponse_NO_CHANGE + return UpdateStoreResponse_NO_CHANGE } -type GetFeastCoreVersionRequest struct { - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} +// Request to create a project +type CreateProjectRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields -func (m *GetFeastCoreVersionRequest) Reset() { *m = GetFeastCoreVersionRequest{} } -func (m *GetFeastCoreVersionRequest) String() string { return proto.CompactTextString(m) } -func (*GetFeastCoreVersionRequest) ProtoMessage() {} -func (*GetFeastCoreVersionRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_d9be266444105411, []int{8} + // Name of project (required) + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` } -func (m *GetFeastCoreVersionRequest) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_GetFeastCoreVersionRequest.Unmarshal(m, b) +func (x *CreateProjectRequest) Reset() { + *x = CreateProjectRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *GetFeastCoreVersionRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_GetFeastCoreVersionRequest.Marshal(b, m, deterministic) + +func (x *CreateProjectRequest) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *GetFeastCoreVersionRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_GetFeastCoreVersionRequest.Merge(m, src) + +func (*CreateProjectRequest) ProtoMessage() {} + +func (x *CreateProjectRequest) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -func (m *GetFeastCoreVersionRequest) XXX_Size() int { - return xxx_messageInfo_GetFeastCoreVersionRequest.Size(m) + +// Deprecated: Use CreateProjectRequest.ProtoReflect.Descriptor instead. +func (*CreateProjectRequest) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{12} } -func (m *GetFeastCoreVersionRequest) XXX_DiscardUnknown() { - xxx_messageInfo_GetFeastCoreVersionRequest.DiscardUnknown(m) + +func (x *CreateProjectRequest) GetName() string { + if x != nil { + return x.Name + } + return "" } -var xxx_messageInfo_GetFeastCoreVersionRequest proto.InternalMessageInfo +// Response for creation of a project +type CreateProjectResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} -type GetFeastCoreVersionResponse struct { - Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` +func (x *CreateProjectResponse) Reset() { + *x = CreateProjectResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *GetFeastCoreVersionResponse) Reset() { *m = GetFeastCoreVersionResponse{} } -func (m *GetFeastCoreVersionResponse) String() string { return proto.CompactTextString(m) } -func (*GetFeastCoreVersionResponse) ProtoMessage() {} -func (*GetFeastCoreVersionResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_d9be266444105411, []int{9} +func (x *CreateProjectResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *GetFeastCoreVersionResponse) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_GetFeastCoreVersionResponse.Unmarshal(m, b) +func (*CreateProjectResponse) ProtoMessage() {} + +func (x *CreateProjectResponse) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -func (m *GetFeastCoreVersionResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_GetFeastCoreVersionResponse.Marshal(b, m, deterministic) + +// Deprecated: Use CreateProjectResponse.ProtoReflect.Descriptor instead. +func (*CreateProjectResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{13} } -func (m *GetFeastCoreVersionResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_GetFeastCoreVersionResponse.Merge(m, src) + +// Request for the archival of a project +type ArchiveProjectRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Name of project to be archived + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` } -func (m *GetFeastCoreVersionResponse) XXX_Size() int { - return xxx_messageInfo_GetFeastCoreVersionResponse.Size(m) + +func (x *ArchiveProjectRequest) Reset() { + *x = ArchiveProjectRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *GetFeastCoreVersionResponse) XXX_DiscardUnknown() { - xxx_messageInfo_GetFeastCoreVersionResponse.DiscardUnknown(m) + +func (x *ArchiveProjectRequest) String() string { + return protoimpl.X.MessageStringOf(x) } -var xxx_messageInfo_GetFeastCoreVersionResponse proto.InternalMessageInfo +func (*ArchiveProjectRequest) ProtoMessage() {} -func (m *GetFeastCoreVersionResponse) GetVersion() string { - if m != nil { - return m.Version +func (x *ArchiveProjectRequest) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return "" + return mi.MessageOf(x) } -type UpdateStoreRequest struct { - Store *Store `protobuf:"bytes,1,opt,name=store,proto3" json:"store,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` +// Deprecated: Use ArchiveProjectRequest.ProtoReflect.Descriptor instead. +func (*ArchiveProjectRequest) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{14} } -func (m *UpdateStoreRequest) Reset() { *m = UpdateStoreRequest{} } -func (m *UpdateStoreRequest) String() string { return proto.CompactTextString(m) } -func (*UpdateStoreRequest) ProtoMessage() {} -func (*UpdateStoreRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_d9be266444105411, []int{10} +func (x *ArchiveProjectRequest) GetName() string { + if x != nil { + return x.Name + } + return "" } -func (m *UpdateStoreRequest) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_UpdateStoreRequest.Unmarshal(m, b) +// Response for archival of a project +type ArchiveProjectResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } -func (m *UpdateStoreRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_UpdateStoreRequest.Marshal(b, m, deterministic) + +func (x *ArchiveProjectResponse) Reset() { + *x = ArchiveProjectResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *UpdateStoreRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_UpdateStoreRequest.Merge(m, src) + +func (x *ArchiveProjectResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *UpdateStoreRequest) XXX_Size() int { - return xxx_messageInfo_UpdateStoreRequest.Size(m) + +func (*ArchiveProjectResponse) ProtoMessage() {} + +func (x *ArchiveProjectResponse) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -func (m *UpdateStoreRequest) XXX_DiscardUnknown() { - xxx_messageInfo_UpdateStoreRequest.DiscardUnknown(m) + +// Deprecated: Use ArchiveProjectResponse.ProtoReflect.Descriptor instead. +func (*ArchiveProjectResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{15} } -var xxx_messageInfo_UpdateStoreRequest proto.InternalMessageInfo +// Request for listing of projects +type ListProjectsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} -func (m *UpdateStoreRequest) GetStore() *Store { - if m != nil { - return m.Store +func (x *ListProjectsRequest) Reset() { + *x = ListProjectsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } - return nil } -type UpdateStoreResponse struct { - Store *Store `protobuf:"bytes,1,opt,name=store,proto3" json:"store,omitempty"` - Status UpdateStoreResponse_Status `protobuf:"varint,2,opt,name=status,proto3,enum=feast.core.UpdateStoreResponse_Status" json:"status,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` +func (x *ListProjectsRequest) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *UpdateStoreResponse) Reset() { *m = UpdateStoreResponse{} } -func (m *UpdateStoreResponse) String() string { return proto.CompactTextString(m) } -func (*UpdateStoreResponse) ProtoMessage() {} -func (*UpdateStoreResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_d9be266444105411, []int{11} +func (*ListProjectsRequest) ProtoMessage() {} + +func (x *ListProjectsRequest) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -func (m *UpdateStoreResponse) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_UpdateStoreResponse.Unmarshal(m, b) +// Deprecated: Use ListProjectsRequest.ProtoReflect.Descriptor instead. +func (*ListProjectsRequest) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{16} } -func (m *UpdateStoreResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_UpdateStoreResponse.Marshal(b, m, deterministic) + +// Response for listing of projects +type ListProjectsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // List of project names (archived projects are filtered out) + Projects []string `protobuf:"bytes,1,rep,name=projects,proto3" json:"projects,omitempty"` } -func (m *UpdateStoreResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_UpdateStoreResponse.Merge(m, src) + +func (x *ListProjectsResponse) Reset() { + *x = ListProjectsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *UpdateStoreResponse) XXX_Size() int { - return xxx_messageInfo_UpdateStoreResponse.Size(m) + +func (x *ListProjectsResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *UpdateStoreResponse) XXX_DiscardUnknown() { - xxx_messageInfo_UpdateStoreResponse.DiscardUnknown(m) + +func (*ListProjectsResponse) ProtoMessage() {} + +func (x *ListProjectsResponse) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_UpdateStoreResponse proto.InternalMessageInfo +// Deprecated: Use ListProjectsResponse.ProtoReflect.Descriptor instead. +func (*ListProjectsResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{17} +} -func (m *UpdateStoreResponse) GetStore() *Store { - if m != nil { - return m.Store +func (x *ListProjectsResponse) GetProjects() []string { + if x != nil { + return x.Projects } return nil } -func (m *UpdateStoreResponse) GetStatus() UpdateStoreResponse_Status { - if m != nil { - return m.Status +// Request for listing ingestion jobs +type ListIngestionJobsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Filter *ListIngestionJobsRequest_Filter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` +} + +func (x *ListIngestionJobsRequest) Reset() { + *x = ListIngestionJobsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } - return UpdateStoreResponse_NO_CHANGE } -// Request to create a project -type CreateProjectRequest struct { - // Name of project (required) - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` +func (x *ListIngestionJobsRequest) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *CreateProjectRequest) Reset() { *m = CreateProjectRequest{} } -func (m *CreateProjectRequest) String() string { return proto.CompactTextString(m) } -func (*CreateProjectRequest) ProtoMessage() {} -func (*CreateProjectRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_d9be266444105411, []int{12} +func (*ListIngestionJobsRequest) ProtoMessage() {} + +func (x *ListIngestionJobsRequest) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -func (m *CreateProjectRequest) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_CreateProjectRequest.Unmarshal(m, b) +// Deprecated: Use ListIngestionJobsRequest.ProtoReflect.Descriptor instead. +func (*ListIngestionJobsRequest) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{18} } -func (m *CreateProjectRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_CreateProjectRequest.Marshal(b, m, deterministic) + +func (x *ListIngestionJobsRequest) GetFilter() *ListIngestionJobsRequest_Filter { + if x != nil { + return x.Filter + } + return nil } -func (m *CreateProjectRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_CreateProjectRequest.Merge(m, src) + +// Response from listing ingestion jobs +type ListIngestionJobsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Jobs []*IngestionJob `protobuf:"bytes,1,rep,name=jobs,proto3" json:"jobs,omitempty"` } -func (m *CreateProjectRequest) XXX_Size() int { - return xxx_messageInfo_CreateProjectRequest.Size(m) + +func (x *ListIngestionJobsResponse) Reset() { + *x = ListIngestionJobsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *CreateProjectRequest) XXX_DiscardUnknown() { - xxx_messageInfo_CreateProjectRequest.DiscardUnknown(m) + +func (x *ListIngestionJobsResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -var xxx_messageInfo_CreateProjectRequest proto.InternalMessageInfo +func (*ListIngestionJobsResponse) ProtoMessage() {} -func (m *CreateProjectRequest) GetName() string { - if m != nil { - return m.Name +func (x *ListIngestionJobsResponse) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return "" + return mi.MessageOf(x) } -// Response for creation of a project -type CreateProjectResponse struct { - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` +// Deprecated: Use ListIngestionJobsResponse.ProtoReflect.Descriptor instead. +func (*ListIngestionJobsResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{19} } -func (m *CreateProjectResponse) Reset() { *m = CreateProjectResponse{} } -func (m *CreateProjectResponse) String() string { return proto.CompactTextString(m) } -func (*CreateProjectResponse) ProtoMessage() {} -func (*CreateProjectResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_d9be266444105411, []int{13} +func (x *ListIngestionJobsResponse) GetJobs() []*IngestionJob { + if x != nil { + return x.Jobs + } + return nil } -func (m *CreateProjectResponse) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_CreateProjectResponse.Unmarshal(m, b) +// Request to restart ingestion job +type RestartIngestionJobRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Job ID assigned by Feast + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` } -func (m *CreateProjectResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_CreateProjectResponse.Marshal(b, m, deterministic) + +func (x *RestartIngestionJobRequest) Reset() { + *x = RestartIngestionJobRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *CreateProjectResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_CreateProjectResponse.Merge(m, src) + +func (x *RestartIngestionJobRequest) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *CreateProjectResponse) XXX_Size() int { - return xxx_messageInfo_CreateProjectResponse.Size(m) + +func (*RestartIngestionJobRequest) ProtoMessage() {} + +func (x *RestartIngestionJobRequest) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -func (m *CreateProjectResponse) XXX_DiscardUnknown() { - xxx_messageInfo_CreateProjectResponse.DiscardUnknown(m) + +// Deprecated: Use RestartIngestionJobRequest.ProtoReflect.Descriptor instead. +func (*RestartIngestionJobRequest) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{20} } -var xxx_messageInfo_CreateProjectResponse proto.InternalMessageInfo +func (x *RestartIngestionJobRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} -// Request for the archival of a project -type ArchiveProjectRequest struct { - // Name of project to be archived - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` +// Response from restartingan injestion job +type RestartIngestionJobResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } -func (m *ArchiveProjectRequest) Reset() { *m = ArchiveProjectRequest{} } -func (m *ArchiveProjectRequest) String() string { return proto.CompactTextString(m) } -func (*ArchiveProjectRequest) ProtoMessage() {} -func (*ArchiveProjectRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_d9be266444105411, []int{14} +func (x *RestartIngestionJobResponse) Reset() { + *x = RestartIngestionJobResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RestartIngestionJobResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RestartIngestionJobResponse) ProtoMessage() {} + +func (x *RestartIngestionJobResponse) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[21] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -func (m *ArchiveProjectRequest) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_ArchiveProjectRequest.Unmarshal(m, b) +// Deprecated: Use RestartIngestionJobResponse.ProtoReflect.Descriptor instead. +func (*RestartIngestionJobResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{21} } -func (m *ArchiveProjectRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_ArchiveProjectRequest.Marshal(b, m, deterministic) + +// Request to stop ingestion job +type StopIngestionJobRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Job ID assigned by Feast + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` } -func (m *ArchiveProjectRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_ArchiveProjectRequest.Merge(m, src) + +func (x *StopIngestionJobRequest) Reset() { + *x = StopIngestionJobRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *ArchiveProjectRequest) XXX_Size() int { - return xxx_messageInfo_ArchiveProjectRequest.Size(m) + +func (x *StopIngestionJobRequest) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *ArchiveProjectRequest) XXX_DiscardUnknown() { - xxx_messageInfo_ArchiveProjectRequest.DiscardUnknown(m) + +func (*StopIngestionJobRequest) ProtoMessage() {} + +func (x *StopIngestionJobRequest) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_ArchiveProjectRequest proto.InternalMessageInfo +// Deprecated: Use StopIngestionJobRequest.ProtoReflect.Descriptor instead. +func (*StopIngestionJobRequest) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{22} +} -func (m *ArchiveProjectRequest) GetName() string { - if m != nil { - return m.Name +func (x *StopIngestionJobRequest) GetId() string { + if x != nil { + return x.Id } return "" } -// Response for archival of a project -type ArchiveProjectResponse struct { - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` +// Request from stopping an ingestion job +type StopIngestionJobResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } -func (m *ArchiveProjectResponse) Reset() { *m = ArchiveProjectResponse{} } -func (m *ArchiveProjectResponse) String() string { return proto.CompactTextString(m) } -func (*ArchiveProjectResponse) ProtoMessage() {} -func (*ArchiveProjectResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_d9be266444105411, []int{15} +func (x *StopIngestionJobResponse) Reset() { + *x = StopIngestionJobResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *ArchiveProjectResponse) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_ArchiveProjectResponse.Unmarshal(m, b) +func (x *StopIngestionJobResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *ArchiveProjectResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_ArchiveProjectResponse.Marshal(b, m, deterministic) + +func (*StopIngestionJobResponse) ProtoMessage() {} + +func (x *StopIngestionJobResponse) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StopIngestionJobResponse.ProtoReflect.Descriptor instead. +func (*StopIngestionJobResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{23} } -func (m *ArchiveProjectResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_ArchiveProjectResponse.Merge(m, src) + +type ListFeatureSetsRequest_Filter struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Name of project that the feature sets belongs to. This can be one of + // - [project_name] + // - * + // If an asterisk is provided, filtering on projects will be disabled. All projects will + // be matched. It is NOT possible to provide an asterisk with a string in order to do + // pattern matching. + // If unspecified this field will default to the default project 'default'. + Project string `protobuf:"bytes,3,opt,name=project,proto3" json:"project,omitempty"` + // Name of the desired feature set. Asterisks can be used as wildcards in the name. + // Matching on names is only permitted if a specific project is defined. It is disallowed + // If the project name is set to "*" + // e.g. + // - * can be used to match all feature sets + // - my-feature-set* can be used to match all features prefixed by "my-feature-set" + // - my-feature-set-6 can be used to select a single feature set + FeatureSetName string `protobuf:"bytes,1,opt,name=feature_set_name,json=featureSetName,proto3" json:"feature_set_name,omitempty"` } -func (m *ArchiveProjectResponse) XXX_Size() int { - return xxx_messageInfo_ArchiveProjectResponse.Size(m) + +func (x *ListFeatureSetsRequest_Filter) Reset() { + *x = ListFeatureSetsRequest_Filter{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *ArchiveProjectResponse) XXX_DiscardUnknown() { - xxx_messageInfo_ArchiveProjectResponse.DiscardUnknown(m) + +func (x *ListFeatureSetsRequest_Filter) String() string { + return protoimpl.X.MessageStringOf(x) } -var xxx_messageInfo_ArchiveProjectResponse proto.InternalMessageInfo +func (*ListFeatureSetsRequest_Filter) ProtoMessage() {} -// Request for listing of projects -type ListProjectsRequest struct { - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` +func (x *ListFeatureSetsRequest_Filter) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -func (m *ListProjectsRequest) Reset() { *m = ListProjectsRequest{} } -func (m *ListProjectsRequest) String() string { return proto.CompactTextString(m) } -func (*ListProjectsRequest) ProtoMessage() {} -func (*ListProjectsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_d9be266444105411, []int{16} +// Deprecated: Use ListFeatureSetsRequest_Filter.ProtoReflect.Descriptor instead. +func (*ListFeatureSetsRequest_Filter) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{2, 0} } -func (m *ListProjectsRequest) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_ListProjectsRequest.Unmarshal(m, b) +func (x *ListFeatureSetsRequest_Filter) GetProject() string { + if x != nil { + return x.Project + } + return "" } -func (m *ListProjectsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_ListProjectsRequest.Marshal(b, m, deterministic) + +func (x *ListFeatureSetsRequest_Filter) GetFeatureSetName() string { + if x != nil { + return x.FeatureSetName + } + return "" } -func (m *ListProjectsRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_ListProjectsRequest.Merge(m, src) + +type ListStoresRequest_Filter struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Name of desired store. Regex is not supported in this query. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` } -func (m *ListProjectsRequest) XXX_Size() int { - return xxx_messageInfo_ListProjectsRequest.Size(m) + +func (x *ListStoresRequest_Filter) Reset() { + *x = ListStoresRequest_Filter{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *ListProjectsRequest) XXX_DiscardUnknown() { - xxx_messageInfo_ListProjectsRequest.DiscardUnknown(m) + +func (x *ListStoresRequest_Filter) String() string { + return protoimpl.X.MessageStringOf(x) } -var xxx_messageInfo_ListProjectsRequest proto.InternalMessageInfo +func (*ListStoresRequest_Filter) ProtoMessage() {} -// Response for listing of projects -type ListProjectsResponse struct { - // List of project names (archived projects are filtered out) - Projects []string `protobuf:"bytes,1,rep,name=projects,proto3" json:"projects,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` +func (x *ListStoresRequest_Filter) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -func (m *ListProjectsResponse) Reset() { *m = ListProjectsResponse{} } -func (m *ListProjectsResponse) String() string { return proto.CompactTextString(m) } -func (*ListProjectsResponse) ProtoMessage() {} -func (*ListProjectsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_d9be266444105411, []int{17} +// Deprecated: Use ListStoresRequest_Filter.ProtoReflect.Descriptor instead. +func (*ListStoresRequest_Filter) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{4, 0} +} + +func (x *ListStoresRequest_Filter) GetName() string { + if x != nil { + return x.Name + } + return "" } -func (m *ListProjectsResponse) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_ListProjectsResponse.Unmarshal(m, b) +type ListIngestionJobsRequest_Filter struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Filter by Job ID assigned by Feast + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // Filter by ingestion job target feature set. + FeatureSetReference *FeatureSetReference `protobuf:"bytes,2,opt,name=feature_set_reference,json=featureSetReference,proto3" json:"feature_set_reference,omitempty"` + // Filter by Name of store + StoreName string `protobuf:"bytes,3,opt,name=store_name,json=storeName,proto3" json:"store_name,omitempty"` } -func (m *ListProjectsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_ListProjectsResponse.Marshal(b, m, deterministic) + +func (x *ListIngestionJobsRequest_Filter) Reset() { + *x = ListIngestionJobsRequest_Filter{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *ListProjectsResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_ListProjectsResponse.Merge(m, src) + +func (x *ListIngestionJobsRequest_Filter) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *ListProjectsResponse) XXX_Size() int { - return xxx_messageInfo_ListProjectsResponse.Size(m) + +func (*ListIngestionJobsRequest_Filter) ProtoMessage() {} + +func (x *ListIngestionJobsRequest_Filter) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[26] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -func (m *ListProjectsResponse) XXX_DiscardUnknown() { - xxx_messageInfo_ListProjectsResponse.DiscardUnknown(m) + +// Deprecated: Use ListIngestionJobsRequest_Filter.ProtoReflect.Descriptor instead. +func (*ListIngestionJobsRequest_Filter) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{18, 0} } -var xxx_messageInfo_ListProjectsResponse proto.InternalMessageInfo +func (x *ListIngestionJobsRequest_Filter) GetId() string { + if x != nil { + return x.Id + } + return "" +} -func (m *ListProjectsResponse) GetProjects() []string { - if m != nil { - return m.Projects +func (x *ListIngestionJobsRequest_Filter) GetFeatureSetReference() *FeatureSetReference { + if x != nil { + return x.FeatureSetReference } return nil } -func init() { - proto.RegisterEnum("feast.core.ApplyFeatureSetResponse_Status", ApplyFeatureSetResponse_Status_name, ApplyFeatureSetResponse_Status_value) - proto.RegisterEnum("feast.core.UpdateStoreResponse_Status", UpdateStoreResponse_Status_name, UpdateStoreResponse_Status_value) - proto.RegisterType((*GetFeatureSetRequest)(nil), "feast.core.GetFeatureSetRequest") - proto.RegisterType((*GetFeatureSetResponse)(nil), "feast.core.GetFeatureSetResponse") - proto.RegisterType((*ListFeatureSetsRequest)(nil), "feast.core.ListFeatureSetsRequest") - proto.RegisterType((*ListFeatureSetsRequest_Filter)(nil), "feast.core.ListFeatureSetsRequest.Filter") - proto.RegisterType((*ListFeatureSetsResponse)(nil), "feast.core.ListFeatureSetsResponse") - proto.RegisterType((*ListStoresRequest)(nil), "feast.core.ListStoresRequest") - proto.RegisterType((*ListStoresRequest_Filter)(nil), "feast.core.ListStoresRequest.Filter") - proto.RegisterType((*ListStoresResponse)(nil), "feast.core.ListStoresResponse") - proto.RegisterType((*ApplyFeatureSetRequest)(nil), "feast.core.ApplyFeatureSetRequest") - proto.RegisterType((*ApplyFeatureSetResponse)(nil), "feast.core.ApplyFeatureSetResponse") - proto.RegisterType((*GetFeastCoreVersionRequest)(nil), "feast.core.GetFeastCoreVersionRequest") - proto.RegisterType((*GetFeastCoreVersionResponse)(nil), "feast.core.GetFeastCoreVersionResponse") - proto.RegisterType((*UpdateStoreRequest)(nil), "feast.core.UpdateStoreRequest") - proto.RegisterType((*UpdateStoreResponse)(nil), "feast.core.UpdateStoreResponse") - proto.RegisterType((*CreateProjectRequest)(nil), "feast.core.CreateProjectRequest") - proto.RegisterType((*CreateProjectResponse)(nil), "feast.core.CreateProjectResponse") - proto.RegisterType((*ArchiveProjectRequest)(nil), "feast.core.ArchiveProjectRequest") - proto.RegisterType((*ArchiveProjectResponse)(nil), "feast.core.ArchiveProjectResponse") - proto.RegisterType((*ListProjectsRequest)(nil), "feast.core.ListProjectsRequest") - proto.RegisterType((*ListProjectsResponse)(nil), "feast.core.ListProjectsResponse") -} - -func init() { proto.RegisterFile("feast/core/CoreService.proto", fileDescriptor_d9be266444105411) } - -var fileDescriptor_d9be266444105411 = []byte{ - // 762 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x56, 0xef, 0x4e, 0x13, 0x4f, - 0x14, 0xfd, 0x2d, 0xfc, 0x28, 0xf6, 0x16, 0xb0, 0x4c, 0x69, 0x69, 0x16, 0x84, 0x3a, 0x12, 0x41, - 0x4c, 0x76, 0x93, 0xfa, 0x81, 0x98, 0x88, 0x49, 0x29, 0x05, 0x13, 0x4d, 0x29, 0x0b, 0x68, 0xc2, - 0x07, 0x49, 0x29, 0x53, 0x28, 0xff, 0xa6, 0xee, 0x4c, 0x49, 0x4c, 0x7c, 0x1a, 0xe3, 0xbb, 0xf8, - 0x0c, 0xbe, 0x8d, 0xd9, 0x9d, 0x69, 0x77, 0x66, 0xba, 0xdd, 0x1a, 0xfd, 0xd6, 0xbd, 0x73, 0xee, - 0xd9, 0xbb, 0xe7, 0xde, 0x73, 0x3b, 0xb0, 0xdc, 0x26, 0x4d, 0xc6, 0xdd, 0x16, 0xf5, 0x89, 0x5b, - 0xa5, 0x3e, 0x39, 0x22, 0xfe, 0x43, 0xa7, 0x45, 0x9c, 0xae, 0x4f, 0x39, 0x45, 0x10, 0x9e, 0x3a, - 0xc1, 0xa9, 0xbd, 0xa4, 0x20, 0xf7, 0x48, 0x93, 0xf7, 0x02, 0x30, 0x17, 0x40, 0xbb, 0xa0, 0x1c, - 0x1e, 0x71, 0xea, 0x4b, 0x02, 0xfc, 0x19, 0x16, 0xf6, 0x09, 0x8f, 0xe0, 0x1e, 0xf9, 0xd2, 0x23, - 0x8c, 0xa3, 0x22, 0x4c, 0x77, 0x7d, 0x7a, 0x4d, 0x5a, 0xbc, 0x38, 0x59, 0xb2, 0x36, 0xd2, 0x5e, - 0xff, 0x11, 0x21, 0xf8, 0xff, 0xbe, 0x79, 0x47, 0x8a, 0x56, 0x18, 0x0e, 0x7f, 0x07, 0xe8, 0x07, - 0xe2, 0xb3, 0x0e, 0xbd, 0x2f, 0x4e, 0x94, 0xac, 0x8d, 0x29, 0xaf, 0xff, 0x88, 0x1b, 0x90, 0x37, - 0xf8, 0x59, 0x97, 0xde, 0x33, 0x82, 0xb6, 0x20, 0xd3, 0x16, 0xd1, 0x33, 0x46, 0x78, 0xc8, 0x96, - 0x29, 0x17, 0x9c, 0xe8, 0x7b, 0x1c, 0x25, 0x09, 0xda, 0x83, 0xdf, 0xf8, 0x97, 0x05, 0x85, 0x0f, - 0x1d, 0xa6, 0x70, 0xb2, 0x7e, 0xd1, 0x15, 0x48, 0xb5, 0x3b, 0xb7, 0x9c, 0xf8, 0x92, 0xee, 0x85, - 0x4a, 0x17, 0x9f, 0xe3, 0xec, 0x85, 0x09, 0x9e, 0x4c, 0xb4, 0xbf, 0x41, 0x4a, 0x44, 0x12, 0x14, - 0xd8, 0x80, 0xac, 0x52, 0xfa, 0x99, 0xa2, 0xc6, 0x5c, 0x54, 0x67, 0x3d, 0xd0, 0xc5, 0x81, 0x9c, - 0x8a, 0x54, 0x35, 0x4a, 0x7b, 0xf3, 0x11, 0xf8, 0xa3, 0x54, 0xeb, 0x18, 0x16, 0x87, 0xca, 0x94, - 0x7a, 0xbd, 0x86, 0x19, 0x85, 0x8a, 0x15, 0xad, 0xd2, 0x64, 0x82, 0x60, 0x99, 0x88, 0x9b, 0x61, - 0x0a, 0xf3, 0x01, 0x6b, 0xd8, 0xf6, 0x81, 0x56, 0x6f, 0x0c, 0xad, 0xd6, 0x4c, 0xad, 0x34, 0xb8, - 0x29, 0xd3, 0xf2, 0x40, 0xa6, 0x98, 0x71, 0xc0, 0xdb, 0x80, 0x54, 0x06, 0xf9, 0x05, 0xeb, 0x30, - 0xc5, 0x82, 0x88, 0x2c, 0x7d, 0x5e, 0x7d, 0x61, 0x08, 0xf5, 0xc4, 0x39, 0x3e, 0x84, 0x42, 0xa5, - 0xdb, 0xbd, 0xfd, 0x3a, 0x3c, 0x95, 0x7f, 0x3d, 0x34, 0x3f, 0x2d, 0x58, 0x1c, 0xe2, 0xfc, 0xc7, - 0x49, 0x44, 0x3b, 0x90, 0x62, 0xbc, 0xc9, 0x7b, 0x2c, 0x6c, 0xe8, 0x5c, 0x79, 0x53, 0xcd, 0x19, - 0xf1, 0x36, 0xe7, 0x28, 0xcc, 0xf0, 0x64, 0x26, 0x76, 0x21, 0x25, 0x22, 0x68, 0x16, 0xd2, 0xf5, - 0x83, 0xb3, 0xea, 0xbb, 0x4a, 0x7d, 0xbf, 0x96, 0xfd, 0x0f, 0x65, 0x60, 0xba, 0xea, 0xd5, 0x2a, - 0xc7, 0xb5, 0xdd, 0xac, 0x85, 0xd2, 0x30, 0x55, 0xf3, 0xbc, 0x03, 0x2f, 0x3b, 0x81, 0x97, 0xc1, - 0x16, 0x86, 0x62, 0x3c, 0x58, 0x07, 0x72, 0x72, 0xa4, 0x40, 0x78, 0x0b, 0x96, 0x62, 0x4f, 0xe5, - 0xa7, 0x2a, 0x3e, 0x15, 0xfd, 0x1a, 0xf8, 0x74, 0x1b, 0xd0, 0x49, 0xf7, 0xa2, 0xc9, 0x89, 0xe8, - 0x84, 0xd4, 0x5b, 0x69, 0x99, 0x95, 0xd8, 0xb2, 0x1f, 0x16, 0xe4, 0xb4, 0xfc, 0xe1, 0x9e, 0x27, - 0x12, 0xa0, 0xb7, 0x86, 0x96, 0xcf, 0x55, 0x64, 0x0c, 0xb3, 0xa9, 0xe3, 0x5a, 0x82, 0x8e, 0x27, - 0x8d, 0x5d, 0xa1, 0x23, 0xde, 0x84, 0x85, 0xaa, 0x4f, 0x9a, 0x9c, 0x34, 0x84, 0x95, 0xfb, 0xdf, - 0x19, 0x37, 0xc4, 0x8b, 0x90, 0x37, 0xb0, 0xe2, 0xcd, 0xf8, 0x25, 0xe4, 0x2b, 0x7e, 0xeb, 0xaa, - 0xf3, 0xf0, 0x27, 0x2c, 0x45, 0x28, 0x98, 0x60, 0x49, 0x93, 0x87, 0x5c, 0x60, 0x12, 0x19, 0xee, - 0x1b, 0x0d, 0x97, 0x61, 0x41, 0x0f, 0x4b, 0x25, 0x6d, 0x78, 0x24, 0xf7, 0x8f, 0xf0, 0x7e, 0xda, - 0x1b, 0x3c, 0x97, 0xbf, 0xa7, 0x20, 0xa3, 0xfc, 0x37, 0xa0, 0x36, 0xe4, 0x62, 0xa6, 0x00, 0x69, - 0x9a, 0x8e, 0x1e, 0x22, 0x7b, 0x7d, 0x2c, 0x4e, 0xd6, 0x74, 0x0c, 0xb3, 0xda, 0x72, 0x47, 0xa5, - 0xe1, 0x4c, 0xdd, 0xc1, 0xf6, 0xd3, 0x04, 0x84, 0x64, 0x3d, 0x85, 0xc7, 0xc6, 0x12, 0x44, 0x78, - 0xfc, 0x22, 0xb7, 0x9f, 0x25, 0x62, 0x24, 0xf7, 0x7b, 0x80, 0x68, 0x33, 0xa1, 0x27, 0x89, 0x3b, - 0xcf, 0x5e, 0x19, 0x75, 0x1c, 0x15, 0x6a, 0xb8, 0x5c, 0x2f, 0x34, 0x7e, 0x89, 0xe9, 0x85, 0x8e, - 0x5a, 0x4a, 0x75, 0xc8, 0x28, 0x53, 0x8f, 0x56, 0x46, 0xda, 0x41, 0x70, 0xae, 0x8e, 0xb1, 0x4b, - 0xd0, 0x2a, 0x6d, 0x9a, 0xf5, 0x56, 0xc5, 0x99, 0x42, 0x6f, 0x55, 0xac, 0x15, 0xd0, 0x27, 0x98, - 0xd3, 0xa7, 0x1b, 0x69, 0x49, 0xb1, 0x36, 0xb1, 0x71, 0x12, 0x44, 0x12, 0x1f, 0xc2, 0x8c, 0xea, - 0x02, 0xb4, 0x6a, 0xb6, 0xc2, 0xb0, 0x8d, 0x5d, 0x1a, 0x0d, 0x10, 0x94, 0x3b, 0x07, 0xa0, 0x5c, - 0x96, 0x76, 0xb2, 0x8a, 0x5f, 0x1a, 0xc1, 0x4d, 0xe8, 0xd4, 0xbd, 0xec, 0xf0, 0xab, 0xde, 0xb9, - 0xd3, 0xa2, 0x77, 0xee, 0x25, 0xbd, 0x26, 0x37, 0xae, 0xb8, 0x32, 0xb1, 0x8b, 0x1b, 0xf7, 0x92, - 0xba, 0xe1, 0x75, 0x89, 0xb9, 0xd1, 0x35, 0xea, 0x3c, 0x15, 0x86, 0x5e, 0xfd, 0x0e, 0x00, 0x00, - 0xff, 0xff, 0xe2, 0x7d, 0x9e, 0xca, 0xa2, 0x09, 0x00, 0x00, +func (x *ListIngestionJobsRequest_Filter) GetStoreName() string { + if x != nil { + return x.StoreName + } + return "" +} + +var File_feast_core_CoreService_proto protoreflect.FileDescriptor + +var file_feast_core_CoreService_proto_rawDesc = []byte{ + 0x0a, 0x1c, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x43, 0x6f, 0x72, + 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, + 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x1a, 0x1b, 0x66, 0x65, 0x61, 0x73, + 0x74, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, + 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x16, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x63, + 0x6f, 0x72, 0x65, 0x2f, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, + 0x24, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1d, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x63, 0x6f, 0x72, + 0x65, 0x2f, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x44, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, + 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, + 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x50, 0x0a, 0x15, 0x47, 0x65, + 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0b, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, + 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, + 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, + 0x52, 0x0a, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x22, 0xa9, 0x01, 0x0a, + 0x16, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x41, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, + 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, + 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x6c, 0x74, + 0x65, 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x1a, 0x4c, 0x0a, 0x06, 0x46, 0x69, + 0x6c, 0x74, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x28, + 0x0a, 0x10, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x53, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x54, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, + 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x66, 0x65, 0x61, 0x73, + 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, + 0x74, 0x52, 0x0b, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x73, 0x22, 0x6f, + 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x3c, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, + 0x72, 0x1a, 0x1c, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, + 0x3d, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x05, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, + 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x05, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x22, 0x51, + 0x0a, 0x16, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x37, 0x0a, 0x0b, 0x66, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, + 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x0a, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, + 0x74, 0x22, 0xd4, 0x01, 0x0a, 0x17, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, + 0x0b, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x0a, 0x66, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x12, 0x42, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x3c, 0x0a, 0x06, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, + 0x45, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, + 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55, + 0x50, 0x44, 0x41, 0x54, 0x45, 0x44, 0x10, 0x03, 0x22, 0x1c, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x46, + 0x65, 0x61, 0x73, 0x74, 0x43, 0x6f, 0x72, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x37, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, + 0x73, 0x74, 0x43, 0x6f, 0x72, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, + 0x3d, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x05, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, + 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x05, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x22, 0xa4, + 0x01, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x05, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x05, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x12, + 0x3e, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x26, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, + 0x24, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x5f, + 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x50, 0x44, 0x41, + 0x54, 0x45, 0x44, 0x10, 0x01, 0x22, 0x2a, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, + 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x22, 0x17, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x6a, 0x65, + 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b, 0x0a, 0x15, 0x41, 0x72, + 0x63, 0x68, 0x69, 0x76, 0x65, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x18, 0x0a, 0x16, 0x41, 0x72, 0x63, 0x68, 0x69, + 0x76, 0x65, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x15, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x32, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, + 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x22, 0xee, 0x01, 0x0a, + 0x18, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, + 0x62, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x43, 0x0a, 0x06, 0x66, 0x69, 0x6c, + 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x66, 0x65, 0x61, 0x73, + 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x67, 0x65, 0x73, + 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, + 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x1a, 0x8c, + 0x01, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x53, 0x0a, 0x15, 0x66, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, + 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, + 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, + 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x13, 0x66, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x1d, + 0x0a, 0x0a, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x49, 0x0a, + 0x19, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, + 0x62, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x04, 0x6a, 0x6f, + 0x62, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, + 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x4a, + 0x6f, 0x62, 0x52, 0x04, 0x6a, 0x6f, 0x62, 0x73, 0x22, 0x2c, 0x0a, 0x1a, 0x52, 0x65, 0x73, 0x74, + 0x61, 0x72, 0x74, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x1d, 0x0a, 0x1b, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x29, 0x0a, 0x17, 0x53, 0x74, 0x6f, 0x70, 0x49, 0x6e, 0x67, + 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, + 0x22, 0x1a, 0x0a, 0x18, 0x53, 0x74, 0x6f, 0x70, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, + 0x6e, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xcb, 0x08, 0x0a, + 0x0b, 0x43, 0x6f, 0x72, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x66, 0x0a, 0x13, + 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x73, 0x74, 0x43, 0x6f, 0x72, 0x65, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, + 0x2e, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x73, 0x74, 0x43, 0x6f, 0x72, 0x65, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x66, 0x65, + 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x73, + 0x74, 0x43, 0x6f, 0x72, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x53, 0x65, 0x74, 0x12, 0x20, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, + 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, + 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0f, 0x4c, 0x69, + 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x73, 0x12, 0x22, 0x2e, + 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x23, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, + 0x6f, 0x72, 0x65, 0x73, 0x12, 0x1d, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, + 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0f, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x12, 0x22, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x66, 0x65, 0x61, + 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x46, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x4e, 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x1e, + 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, + 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x54, 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, + 0x12, 0x20, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0e, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, + 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x21, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, + 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x50, 0x72, 0x6f, 0x6a, + 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x66, 0x65, 0x61, + 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x50, + 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, + 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1f, + 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x20, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x60, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, + 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x73, 0x12, 0x24, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, + 0x6e, 0x4a, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x66, + 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, + 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x66, 0x0a, 0x13, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x49, 0x6e, + 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x12, 0x26, 0x2e, 0x66, 0x65, 0x61, + 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x49, + 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, + 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, + 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x10, 0x53, + 0x74, 0x6f, 0x70, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x12, + 0x23, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x74, 0x6f, + 0x70, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, + 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x4a, + 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x59, 0x0a, 0x10, 0x66, 0x65, + 0x61, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x42, 0x10, + 0x43, 0x6f, 0x72, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, + 0x5a, 0x33, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x66, 0x65, 0x61, + 0x73, 0x74, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x73, 0x64, 0x6b, + 0x2f, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, + 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_feast_core_CoreService_proto_rawDescOnce sync.Once + file_feast_core_CoreService_proto_rawDescData = file_feast_core_CoreService_proto_rawDesc +) + +func file_feast_core_CoreService_proto_rawDescGZIP() []byte { + file_feast_core_CoreService_proto_rawDescOnce.Do(func() { + file_feast_core_CoreService_proto_rawDescData = protoimpl.X.CompressGZIP(file_feast_core_CoreService_proto_rawDescData) + }) + return file_feast_core_CoreService_proto_rawDescData +} + +var file_feast_core_CoreService_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_feast_core_CoreService_proto_msgTypes = make([]protoimpl.MessageInfo, 27) +var file_feast_core_CoreService_proto_goTypes = []interface{}{ + (ApplyFeatureSetResponse_Status)(0), // 0: feast.core.ApplyFeatureSetResponse.Status + (UpdateStoreResponse_Status)(0), // 1: feast.core.UpdateStoreResponse.Status + (*GetFeatureSetRequest)(nil), // 2: feast.core.GetFeatureSetRequest + (*GetFeatureSetResponse)(nil), // 3: feast.core.GetFeatureSetResponse + (*ListFeatureSetsRequest)(nil), // 4: feast.core.ListFeatureSetsRequest + (*ListFeatureSetsResponse)(nil), // 5: feast.core.ListFeatureSetsResponse + (*ListStoresRequest)(nil), // 6: feast.core.ListStoresRequest + (*ListStoresResponse)(nil), // 7: feast.core.ListStoresResponse + (*ApplyFeatureSetRequest)(nil), // 8: feast.core.ApplyFeatureSetRequest + (*ApplyFeatureSetResponse)(nil), // 9: feast.core.ApplyFeatureSetResponse + (*GetFeastCoreVersionRequest)(nil), // 10: feast.core.GetFeastCoreVersionRequest + (*GetFeastCoreVersionResponse)(nil), // 11: feast.core.GetFeastCoreVersionResponse + (*UpdateStoreRequest)(nil), // 12: feast.core.UpdateStoreRequest + (*UpdateStoreResponse)(nil), // 13: feast.core.UpdateStoreResponse + (*CreateProjectRequest)(nil), // 14: feast.core.CreateProjectRequest + (*CreateProjectResponse)(nil), // 15: feast.core.CreateProjectResponse + (*ArchiveProjectRequest)(nil), // 16: feast.core.ArchiveProjectRequest + (*ArchiveProjectResponse)(nil), // 17: feast.core.ArchiveProjectResponse + (*ListProjectsRequest)(nil), // 18: feast.core.ListProjectsRequest + (*ListProjectsResponse)(nil), // 19: feast.core.ListProjectsResponse + (*ListIngestionJobsRequest)(nil), // 20: feast.core.ListIngestionJobsRequest + (*ListIngestionJobsResponse)(nil), // 21: feast.core.ListIngestionJobsResponse + (*RestartIngestionJobRequest)(nil), // 22: feast.core.RestartIngestionJobRequest + (*RestartIngestionJobResponse)(nil), // 23: feast.core.RestartIngestionJobResponse + (*StopIngestionJobRequest)(nil), // 24: feast.core.StopIngestionJobRequest + (*StopIngestionJobResponse)(nil), // 25: feast.core.StopIngestionJobResponse + (*ListFeatureSetsRequest_Filter)(nil), // 26: feast.core.ListFeatureSetsRequest.Filter + (*ListStoresRequest_Filter)(nil), // 27: feast.core.ListStoresRequest.Filter + (*ListIngestionJobsRequest_Filter)(nil), // 28: feast.core.ListIngestionJobsRequest.Filter + (*FeatureSet)(nil), // 29: feast.core.FeatureSet + (*Store)(nil), // 30: feast.core.Store + (*IngestionJob)(nil), // 31: feast.core.IngestionJob + (*FeatureSetReference)(nil), // 32: feast.core.FeatureSetReference +} +var file_feast_core_CoreService_proto_depIdxs = []int32{ + 29, // 0: feast.core.GetFeatureSetResponse.feature_set:type_name -> feast.core.FeatureSet + 26, // 1: feast.core.ListFeatureSetsRequest.filter:type_name -> feast.core.ListFeatureSetsRequest.Filter + 29, // 2: feast.core.ListFeatureSetsResponse.feature_sets:type_name -> feast.core.FeatureSet + 27, // 3: feast.core.ListStoresRequest.filter:type_name -> feast.core.ListStoresRequest.Filter + 30, // 4: feast.core.ListStoresResponse.store:type_name -> feast.core.Store + 29, // 5: feast.core.ApplyFeatureSetRequest.feature_set:type_name -> feast.core.FeatureSet + 29, // 6: feast.core.ApplyFeatureSetResponse.feature_set:type_name -> feast.core.FeatureSet + 0, // 7: feast.core.ApplyFeatureSetResponse.status:type_name -> feast.core.ApplyFeatureSetResponse.Status + 30, // 8: feast.core.UpdateStoreRequest.store:type_name -> feast.core.Store + 30, // 9: feast.core.UpdateStoreResponse.store:type_name -> feast.core.Store + 1, // 10: feast.core.UpdateStoreResponse.status:type_name -> feast.core.UpdateStoreResponse.Status + 28, // 11: feast.core.ListIngestionJobsRequest.filter:type_name -> feast.core.ListIngestionJobsRequest.Filter + 31, // 12: feast.core.ListIngestionJobsResponse.jobs:type_name -> feast.core.IngestionJob + 32, // 13: feast.core.ListIngestionJobsRequest.Filter.feature_set_reference:type_name -> feast.core.FeatureSetReference + 10, // 14: feast.core.CoreService.GetFeastCoreVersion:input_type -> feast.core.GetFeastCoreVersionRequest + 2, // 15: feast.core.CoreService.GetFeatureSet:input_type -> feast.core.GetFeatureSetRequest + 4, // 16: feast.core.CoreService.ListFeatureSets:input_type -> feast.core.ListFeatureSetsRequest + 6, // 17: feast.core.CoreService.ListStores:input_type -> feast.core.ListStoresRequest + 8, // 18: feast.core.CoreService.ApplyFeatureSet:input_type -> feast.core.ApplyFeatureSetRequest + 12, // 19: feast.core.CoreService.UpdateStore:input_type -> feast.core.UpdateStoreRequest + 14, // 20: feast.core.CoreService.CreateProject:input_type -> feast.core.CreateProjectRequest + 16, // 21: feast.core.CoreService.ArchiveProject:input_type -> feast.core.ArchiveProjectRequest + 18, // 22: feast.core.CoreService.ListProjects:input_type -> feast.core.ListProjectsRequest + 20, // 23: feast.core.CoreService.ListIngestionJobs:input_type -> feast.core.ListIngestionJobsRequest + 22, // 24: feast.core.CoreService.RestartIngestionJob:input_type -> feast.core.RestartIngestionJobRequest + 24, // 25: feast.core.CoreService.StopIngestionJob:input_type -> feast.core.StopIngestionJobRequest + 11, // 26: feast.core.CoreService.GetFeastCoreVersion:output_type -> feast.core.GetFeastCoreVersionResponse + 3, // 27: feast.core.CoreService.GetFeatureSet:output_type -> feast.core.GetFeatureSetResponse + 5, // 28: feast.core.CoreService.ListFeatureSets:output_type -> feast.core.ListFeatureSetsResponse + 7, // 29: feast.core.CoreService.ListStores:output_type -> feast.core.ListStoresResponse + 9, // 30: feast.core.CoreService.ApplyFeatureSet:output_type -> feast.core.ApplyFeatureSetResponse + 13, // 31: feast.core.CoreService.UpdateStore:output_type -> feast.core.UpdateStoreResponse + 15, // 32: feast.core.CoreService.CreateProject:output_type -> feast.core.CreateProjectResponse + 17, // 33: feast.core.CoreService.ArchiveProject:output_type -> feast.core.ArchiveProjectResponse + 19, // 34: feast.core.CoreService.ListProjects:output_type -> feast.core.ListProjectsResponse + 21, // 35: feast.core.CoreService.ListIngestionJobs:output_type -> feast.core.ListIngestionJobsResponse + 23, // 36: feast.core.CoreService.RestartIngestionJob:output_type -> feast.core.RestartIngestionJobResponse + 25, // 37: feast.core.CoreService.StopIngestionJob:output_type -> feast.core.StopIngestionJobResponse + 26, // [26:38] is the sub-list for method output_type + 14, // [14:26] is the sub-list for method input_type + 14, // [14:14] is the sub-list for extension type_name + 14, // [14:14] is the sub-list for extension extendee + 0, // [0:14] is the sub-list for field type_name +} + +func init() { file_feast_core_CoreService_proto_init() } +func file_feast_core_CoreService_proto_init() { + if File_feast_core_CoreService_proto != nil { + return + } + file_feast_core_FeatureSet_proto_init() + file_feast_core_Store_proto_init() + file_feast_core_FeatureSetReference_proto_init() + file_feast_core_IngestionJob_proto_init() + if !protoimpl.UnsafeEnabled { + file_feast_core_CoreService_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetFeatureSetRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_CoreService_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetFeatureSetResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_CoreService_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListFeatureSetsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_CoreService_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListFeatureSetsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_CoreService_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListStoresRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_CoreService_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListStoresResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_CoreService_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ApplyFeatureSetRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_CoreService_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ApplyFeatureSetResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_CoreService_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetFeastCoreVersionRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_CoreService_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetFeastCoreVersionResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_CoreService_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateStoreRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_CoreService_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateStoreResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_CoreService_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateProjectRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_CoreService_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateProjectResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_CoreService_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ArchiveProjectRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_CoreService_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ArchiveProjectResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_CoreService_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListProjectsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_CoreService_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListProjectsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_CoreService_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListIngestionJobsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_CoreService_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListIngestionJobsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_CoreService_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RestartIngestionJobRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_CoreService_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RestartIngestionJobResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_CoreService_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StopIngestionJobRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_CoreService_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StopIngestionJobResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_CoreService_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListFeatureSetsRequest_Filter); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_CoreService_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListStoresRequest_Filter); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_CoreService_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListIngestionJobsRequest_Filter); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_feast_core_CoreService_proto_rawDesc, + NumEnums: 2, + NumMessages: 27, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_feast_core_CoreService_proto_goTypes, + DependencyIndexes: file_feast_core_CoreService_proto_depIdxs, + EnumInfos: file_feast_core_CoreService_proto_enumTypes, + MessageInfos: file_feast_core_CoreService_proto_msgTypes, + }.Build() + File_feast_core_CoreService_proto = out.File + file_feast_core_CoreService_proto_rawDesc = nil + file_feast_core_CoreService_proto_goTypes = nil + file_feast_core_CoreService_proto_depIdxs = nil } // Reference imports to suppress errors if they are not otherwise used. var _ context.Context -var _ grpc.ClientConn +var _ grpc.ClientConnInterface // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion4 +const _ = grpc.SupportPackageIsVersion6 // CoreServiceClient is the client API for CoreService service. // @@ -1026,8 +2147,11 @@ type CoreServiceClient interface { // Create or update and existing feature set. // // This function is idempotent - it will not create a new feature set if schema does not change. - // If an existing feature set is updated, core will advance the version number, which will be - // returned in response. + // Schema changes will update the feature set if the changes are valid. + // All changes except the following are valid: + // - Changes to feature set id (name, project) + // - Changes to entities + // - Changes to feature name and type ApplyFeatureSet(ctx context.Context, in *ApplyFeatureSetRequest, opts ...grpc.CallOption) (*ApplyFeatureSetResponse, error) // Updates core with the configuration of the store. // @@ -1035,8 +2159,8 @@ type CoreServiceClient interface { // start or update the necessary feature population jobs for the updated store. UpdateStore(ctx context.Context, in *UpdateStoreRequest, opts ...grpc.CallOption) (*UpdateStoreResponse, error) // Creates a project. Projects serve as namespaces within which resources like features will be - // created. Both feature set names as well as field names must be unique within a project. Project - // names themselves must be globally unique. + // created. Feature set names as must be unique within a project while field (Feature/Entity) names + // must be unique within a Feature Set. Project names themselves must be globally unique. CreateProject(ctx context.Context, in *CreateProjectRequest, opts ...grpc.CallOption) (*CreateProjectResponse, error) // Archives a project. Archived projects will continue to exist and function, but won't be visible // through the Core API. Any existing ingestion or serving requests will continue to function, @@ -1045,13 +2169,27 @@ type CoreServiceClient interface { ArchiveProject(ctx context.Context, in *ArchiveProjectRequest, opts ...grpc.CallOption) (*ArchiveProjectResponse, error) // Lists all projects active projects. ListProjects(ctx context.Context, in *ListProjectsRequest, opts ...grpc.CallOption) (*ListProjectsResponse, error) + // List Ingestion Jobs given an optional filter. + // Returns allow ingestions matching the given request filter. + // Returns all ingestion jobs if no filter is provided. + // Returns an empty list if no ingestion jobs match the filter. + ListIngestionJobs(ctx context.Context, in *ListIngestionJobsRequest, opts ...grpc.CallOption) (*ListIngestionJobsResponse, error) + // Restart an Ingestion Job. Restarts the ingestion job with the given job id. + // NOTE: Data might be lost during the restart for some job runners. + // Does not support stopping a job in a transitional (ie pending, suspending, aborting), + // terminal state (ie suspended or aborted) or unknown status + RestartIngestionJob(ctx context.Context, in *RestartIngestionJobRequest, opts ...grpc.CallOption) (*RestartIngestionJobResponse, error) + // Stop an Ingestion Job. Stop (Aborts) the ingestion job with the given job id. + // Does nothing if the target job if already in a terminal state (ie suspended or aborted). + // Does not support stopping a job in a transitional (ie pending, suspending, aborting) or unknown status + StopIngestionJob(ctx context.Context, in *StopIngestionJobRequest, opts ...grpc.CallOption) (*StopIngestionJobResponse, error) } type coreServiceClient struct { - cc *grpc.ClientConn + cc grpc.ClientConnInterface } -func NewCoreServiceClient(cc *grpc.ClientConn) CoreServiceClient { +func NewCoreServiceClient(cc grpc.ClientConnInterface) CoreServiceClient { return &coreServiceClient{cc} } @@ -1136,6 +2274,33 @@ func (c *coreServiceClient) ListProjects(ctx context.Context, in *ListProjectsRe return out, nil } +func (c *coreServiceClient) ListIngestionJobs(ctx context.Context, in *ListIngestionJobsRequest, opts ...grpc.CallOption) (*ListIngestionJobsResponse, error) { + out := new(ListIngestionJobsResponse) + err := c.cc.Invoke(ctx, "/feast.core.CoreService/ListIngestionJobs", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *coreServiceClient) RestartIngestionJob(ctx context.Context, in *RestartIngestionJobRequest, opts ...grpc.CallOption) (*RestartIngestionJobResponse, error) { + out := new(RestartIngestionJobResponse) + err := c.cc.Invoke(ctx, "/feast.core.CoreService/RestartIngestionJob", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *coreServiceClient) StopIngestionJob(ctx context.Context, in *StopIngestionJobRequest, opts ...grpc.CallOption) (*StopIngestionJobResponse, error) { + out := new(StopIngestionJobResponse) + err := c.cc.Invoke(ctx, "/feast.core.CoreService/StopIngestionJob", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // CoreServiceServer is the server API for CoreService service. type CoreServiceServer interface { // Retrieve version information about this Feast deployment @@ -1158,8 +2323,11 @@ type CoreServiceServer interface { // Create or update and existing feature set. // // This function is idempotent - it will not create a new feature set if schema does not change. - // If an existing feature set is updated, core will advance the version number, which will be - // returned in response. + // Schema changes will update the feature set if the changes are valid. + // All changes except the following are valid: + // - Changes to feature set id (name, project) + // - Changes to entities + // - Changes to feature name and type ApplyFeatureSet(context.Context, *ApplyFeatureSetRequest) (*ApplyFeatureSetResponse, error) // Updates core with the configuration of the store. // @@ -1167,8 +2335,8 @@ type CoreServiceServer interface { // start or update the necessary feature population jobs for the updated store. UpdateStore(context.Context, *UpdateStoreRequest) (*UpdateStoreResponse, error) // Creates a project. Projects serve as namespaces within which resources like features will be - // created. Both feature set names as well as field names must be unique within a project. Project - // names themselves must be globally unique. + // created. Feature set names as must be unique within a project while field (Feature/Entity) names + // must be unique within a Feature Set. Project names themselves must be globally unique. CreateProject(context.Context, *CreateProjectRequest) (*CreateProjectResponse, error) // Archives a project. Archived projects will continue to exist and function, but won't be visible // through the Core API. Any existing ingestion or serving requests will continue to function, @@ -1177,39 +2345,62 @@ type CoreServiceServer interface { ArchiveProject(context.Context, *ArchiveProjectRequest) (*ArchiveProjectResponse, error) // Lists all projects active projects. ListProjects(context.Context, *ListProjectsRequest) (*ListProjectsResponse, error) + // List Ingestion Jobs given an optional filter. + // Returns allow ingestions matching the given request filter. + // Returns all ingestion jobs if no filter is provided. + // Returns an empty list if no ingestion jobs match the filter. + ListIngestionJobs(context.Context, *ListIngestionJobsRequest) (*ListIngestionJobsResponse, error) + // Restart an Ingestion Job. Restarts the ingestion job with the given job id. + // NOTE: Data might be lost during the restart for some job runners. + // Does not support stopping a job in a transitional (ie pending, suspending, aborting), + // terminal state (ie suspended or aborted) or unknown status + RestartIngestionJob(context.Context, *RestartIngestionJobRequest) (*RestartIngestionJobResponse, error) + // Stop an Ingestion Job. Stop (Aborts) the ingestion job with the given job id. + // Does nothing if the target job if already in a terminal state (ie suspended or aborted). + // Does not support stopping a job in a transitional (ie pending, suspending, aborting) or unknown status + StopIngestionJob(context.Context, *StopIngestionJobRequest) (*StopIngestionJobResponse, error) } // UnimplementedCoreServiceServer can be embedded to have forward compatible implementations. type UnimplementedCoreServiceServer struct { } -func (*UnimplementedCoreServiceServer) GetFeastCoreVersion(ctx context.Context, req *GetFeastCoreVersionRequest) (*GetFeastCoreVersionResponse, error) { +func (*UnimplementedCoreServiceServer) GetFeastCoreVersion(context.Context, *GetFeastCoreVersionRequest) (*GetFeastCoreVersionResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetFeastCoreVersion not implemented") } -func (*UnimplementedCoreServiceServer) GetFeatureSet(ctx context.Context, req *GetFeatureSetRequest) (*GetFeatureSetResponse, error) { +func (*UnimplementedCoreServiceServer) GetFeatureSet(context.Context, *GetFeatureSetRequest) (*GetFeatureSetResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetFeatureSet not implemented") } -func (*UnimplementedCoreServiceServer) ListFeatureSets(ctx context.Context, req *ListFeatureSetsRequest) (*ListFeatureSetsResponse, error) { +func (*UnimplementedCoreServiceServer) ListFeatureSets(context.Context, *ListFeatureSetsRequest) (*ListFeatureSetsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListFeatureSets not implemented") } -func (*UnimplementedCoreServiceServer) ListStores(ctx context.Context, req *ListStoresRequest) (*ListStoresResponse, error) { +func (*UnimplementedCoreServiceServer) ListStores(context.Context, *ListStoresRequest) (*ListStoresResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListStores not implemented") } -func (*UnimplementedCoreServiceServer) ApplyFeatureSet(ctx context.Context, req *ApplyFeatureSetRequest) (*ApplyFeatureSetResponse, error) { +func (*UnimplementedCoreServiceServer) ApplyFeatureSet(context.Context, *ApplyFeatureSetRequest) (*ApplyFeatureSetResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ApplyFeatureSet not implemented") } -func (*UnimplementedCoreServiceServer) UpdateStore(ctx context.Context, req *UpdateStoreRequest) (*UpdateStoreResponse, error) { +func (*UnimplementedCoreServiceServer) UpdateStore(context.Context, *UpdateStoreRequest) (*UpdateStoreResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateStore not implemented") } -func (*UnimplementedCoreServiceServer) CreateProject(ctx context.Context, req *CreateProjectRequest) (*CreateProjectResponse, error) { +func (*UnimplementedCoreServiceServer) CreateProject(context.Context, *CreateProjectRequest) (*CreateProjectResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method CreateProject not implemented") } -func (*UnimplementedCoreServiceServer) ArchiveProject(ctx context.Context, req *ArchiveProjectRequest) (*ArchiveProjectResponse, error) { +func (*UnimplementedCoreServiceServer) ArchiveProject(context.Context, *ArchiveProjectRequest) (*ArchiveProjectResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ArchiveProject not implemented") } -func (*UnimplementedCoreServiceServer) ListProjects(ctx context.Context, req *ListProjectsRequest) (*ListProjectsResponse, error) { +func (*UnimplementedCoreServiceServer) ListProjects(context.Context, *ListProjectsRequest) (*ListProjectsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListProjects not implemented") } +func (*UnimplementedCoreServiceServer) ListIngestionJobs(context.Context, *ListIngestionJobsRequest) (*ListIngestionJobsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListIngestionJobs not implemented") +} +func (*UnimplementedCoreServiceServer) RestartIngestionJob(context.Context, *RestartIngestionJobRequest) (*RestartIngestionJobResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RestartIngestionJob not implemented") +} +func (*UnimplementedCoreServiceServer) StopIngestionJob(context.Context, *StopIngestionJobRequest) (*StopIngestionJobResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method StopIngestionJob not implemented") +} func RegisterCoreServiceServer(s *grpc.Server, srv CoreServiceServer) { s.RegisterService(&_CoreService_serviceDesc, srv) @@ -1377,6 +2568,60 @@ func _CoreService_ListProjects_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _CoreService_ListIngestionJobs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListIngestionJobsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CoreServiceServer).ListIngestionJobs(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/feast.core.CoreService/ListIngestionJobs", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CoreServiceServer).ListIngestionJobs(ctx, req.(*ListIngestionJobsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CoreService_RestartIngestionJob_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RestartIngestionJobRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CoreServiceServer).RestartIngestionJob(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/feast.core.CoreService/RestartIngestionJob", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CoreServiceServer).RestartIngestionJob(ctx, req.(*RestartIngestionJobRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CoreService_StopIngestionJob_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StopIngestionJobRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CoreServiceServer).StopIngestionJob(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/feast.core.CoreService/StopIngestionJob", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CoreServiceServer).StopIngestionJob(ctx, req.(*StopIngestionJobRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _CoreService_serviceDesc = grpc.ServiceDesc{ ServiceName: "feast.core.CoreService", HandlerType: (*CoreServiceServer)(nil), @@ -1417,6 +2662,18 @@ var _CoreService_serviceDesc = grpc.ServiceDesc{ MethodName: "ListProjects", Handler: _CoreService_ListProjects_Handler, }, + { + MethodName: "ListIngestionJobs", + Handler: _CoreService_ListIngestionJobs_Handler, + }, + { + MethodName: "RestartIngestionJob", + Handler: _CoreService_RestartIngestionJob_Handler, + }, + { + MethodName: "StopIngestionJob", + Handler: _CoreService_StopIngestionJob_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "feast/core/CoreService.proto", diff --git a/sdk/go/protos/feast/core/FeatureSet.pb.go b/sdk/go/protos/feast/core/FeatureSet.pb.go index 26d9d9c4f7e..3c6441f2ac9 100644 --- a/sdk/go/protos/feast/core/FeatureSet.pb.go +++ b/sdk/go/protos/feast/core/FeatureSet.pb.go @@ -1,27 +1,49 @@ +// +// * Copyright 2019 The Feast Authors +// * +// * 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.23.0 +// protoc v3.10.0 // source: feast/core/FeatureSet.proto package core import ( - fmt "fmt" - types "github.com/gojek/feast/sdk/go/protos/feast/types" + types "github.com/feast-dev/feast/sdk/go/protos/feast/types" + v0 "github.com/feast-dev/feast/sdk/go/protos/tensorflow_metadata/proto/v0" proto "github.com/golang/protobuf/proto" duration "github.com/golang/protobuf/ptypes/duration" timestamp "github.com/golang/protobuf/ptypes/timestamp" - math "math" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" ) -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf +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 proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 type FeatureSetStatus int32 @@ -31,82 +53,113 @@ const ( FeatureSetStatus_STATUS_READY FeatureSetStatus = 2 ) -var FeatureSetStatus_name = map[int32]string{ - 0: "STATUS_INVALID", - 1: "STATUS_PENDING", - 2: "STATUS_READY", -} +// Enum value maps for FeatureSetStatus. +var ( + FeatureSetStatus_name = map[int32]string{ + 0: "STATUS_INVALID", + 1: "STATUS_PENDING", + 2: "STATUS_READY", + } + FeatureSetStatus_value = map[string]int32{ + "STATUS_INVALID": 0, + "STATUS_PENDING": 1, + "STATUS_READY": 2, + } +) -var FeatureSetStatus_value = map[string]int32{ - "STATUS_INVALID": 0, - "STATUS_PENDING": 1, - "STATUS_READY": 2, +func (x FeatureSetStatus) Enum() *FeatureSetStatus { + p := new(FeatureSetStatus) + *p = x + return p } func (x FeatureSetStatus) String() string { - return proto.EnumName(FeatureSetStatus_name, int32(x)) + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } +func (FeatureSetStatus) Descriptor() protoreflect.EnumDescriptor { + return file_feast_core_FeatureSet_proto_enumTypes[0].Descriptor() +} + +func (FeatureSetStatus) Type() protoreflect.EnumType { + return &file_feast_core_FeatureSet_proto_enumTypes[0] +} + +func (x FeatureSetStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use FeatureSetStatus.Descriptor instead. func (FeatureSetStatus) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_972fbd278ac19c0c, []int{0} + return file_feast_core_FeatureSet_proto_rawDescGZIP(), []int{0} } type FeatureSet struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // User-specified specifications of this feature set. Spec *FeatureSetSpec `protobuf:"bytes,1,opt,name=spec,proto3" json:"spec,omitempty"` // System-populated metadata for this feature set. - Meta *FeatureSetMeta `protobuf:"bytes,2,opt,name=meta,proto3" json:"meta,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Meta *FeatureSetMeta `protobuf:"bytes,2,opt,name=meta,proto3" json:"meta,omitempty"` } -func (m *FeatureSet) Reset() { *m = FeatureSet{} } -func (m *FeatureSet) String() string { return proto.CompactTextString(m) } -func (*FeatureSet) ProtoMessage() {} -func (*FeatureSet) Descriptor() ([]byte, []int) { - return fileDescriptor_972fbd278ac19c0c, []int{0} +func (x *FeatureSet) Reset() { + *x = FeatureSet{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_FeatureSet_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *FeatureSet) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_FeatureSet.Unmarshal(m, b) -} -func (m *FeatureSet) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_FeatureSet.Marshal(b, m, deterministic) -} -func (m *FeatureSet) XXX_Merge(src proto.Message) { - xxx_messageInfo_FeatureSet.Merge(m, src) -} -func (m *FeatureSet) XXX_Size() int { - return xxx_messageInfo_FeatureSet.Size(m) +func (x *FeatureSet) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *FeatureSet) XXX_DiscardUnknown() { - xxx_messageInfo_FeatureSet.DiscardUnknown(m) + +func (*FeatureSet) ProtoMessage() {} + +func (x *FeatureSet) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_FeatureSet_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_FeatureSet proto.InternalMessageInfo +// Deprecated: Use FeatureSet.ProtoReflect.Descriptor instead. +func (*FeatureSet) Descriptor() ([]byte, []int) { + return file_feast_core_FeatureSet_proto_rawDescGZIP(), []int{0} +} -func (m *FeatureSet) GetSpec() *FeatureSetSpec { - if m != nil { - return m.Spec +func (x *FeatureSet) GetSpec() *FeatureSetSpec { + if x != nil { + return x.Spec } return nil } -func (m *FeatureSet) GetMeta() *FeatureSetMeta { - if m != nil { - return m.Meta +func (x *FeatureSet) GetMeta() *FeatureSetMeta { + if x != nil { + return x.Meta } return nil } type FeatureSetSpec struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Name of project that this feature set belongs to. Project string `protobuf:"bytes,7,opt,name=project,proto3" json:"project,omitempty"` // Name of the feature set. Must be unique. Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - // Feature set version. - Version int32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` // List of entities contained within this featureSet. // This allows the feature to be used during joins between feature sets. // If the featureSet is ingested into a store that supports keys, this value @@ -120,185 +173,497 @@ type FeatureSetSpec struct { MaxAge *duration.Duration `protobuf:"bytes,5,opt,name=max_age,json=maxAge,proto3" json:"max_age,omitempty"` // Optional. Source on which feature rows can be found. // If not set, source will be set to the default value configured in Feast Core. - Source *Source `protobuf:"bytes,6,opt,name=source,proto3" json:"source,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Source *Source `protobuf:"bytes,6,opt,name=source,proto3" json:"source,omitempty"` + // User defined metadata + Labels map[string]string `protobuf:"bytes,8,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } -func (m *FeatureSetSpec) Reset() { *m = FeatureSetSpec{} } -func (m *FeatureSetSpec) String() string { return proto.CompactTextString(m) } -func (*FeatureSetSpec) ProtoMessage() {} -func (*FeatureSetSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_972fbd278ac19c0c, []int{1} +func (x *FeatureSetSpec) Reset() { + *x = FeatureSetSpec{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_FeatureSet_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *FeatureSetSpec) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_FeatureSetSpec.Unmarshal(m, b) +func (x *FeatureSetSpec) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *FeatureSetSpec) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_FeatureSetSpec.Marshal(b, m, deterministic) -} -func (m *FeatureSetSpec) XXX_Merge(src proto.Message) { - xxx_messageInfo_FeatureSetSpec.Merge(m, src) -} -func (m *FeatureSetSpec) XXX_Size() int { - return xxx_messageInfo_FeatureSetSpec.Size(m) -} -func (m *FeatureSetSpec) XXX_DiscardUnknown() { - xxx_messageInfo_FeatureSetSpec.DiscardUnknown(m) + +func (*FeatureSetSpec) ProtoMessage() {} + +func (x *FeatureSetSpec) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_FeatureSet_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_FeatureSetSpec proto.InternalMessageInfo +// Deprecated: Use FeatureSetSpec.ProtoReflect.Descriptor instead. +func (*FeatureSetSpec) Descriptor() ([]byte, []int) { + return file_feast_core_FeatureSet_proto_rawDescGZIP(), []int{1} +} -func (m *FeatureSetSpec) GetProject() string { - if m != nil { - return m.Project +func (x *FeatureSetSpec) GetProject() string { + if x != nil { + return x.Project } return "" } -func (m *FeatureSetSpec) GetName() string { - if m != nil { - return m.Name +func (x *FeatureSetSpec) GetName() string { + if x != nil { + return x.Name } return "" } -func (m *FeatureSetSpec) GetVersion() int32 { - if m != nil { - return m.Version +func (x *FeatureSetSpec) GetEntities() []*EntitySpec { + if x != nil { + return x.Entities } - return 0 + return nil } -func (m *FeatureSetSpec) GetEntities() []*EntitySpec { - if m != nil { - return m.Entities +func (x *FeatureSetSpec) GetFeatures() []*FeatureSpec { + if x != nil { + return x.Features } return nil } -func (m *FeatureSetSpec) GetFeatures() []*FeatureSpec { - if m != nil { - return m.Features +func (x *FeatureSetSpec) GetMaxAge() *duration.Duration { + if x != nil { + return x.MaxAge } return nil } -func (m *FeatureSetSpec) GetMaxAge() *duration.Duration { - if m != nil { - return m.MaxAge +func (x *FeatureSetSpec) GetSource() *Source { + if x != nil { + return x.Source } return nil } -func (m *FeatureSetSpec) GetSource() *Source { - if m != nil { - return m.Source +func (x *FeatureSetSpec) GetLabels() map[string]string { + if x != nil { + return x.Labels } return nil } type EntitySpec struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Name of the entity. Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - // Value type of the feature. - ValueType types.ValueType_Enum `protobuf:"varint,2,opt,name=value_type,json=valueType,proto3,enum=feast.types.ValueType_Enum" json:"value_type,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + // Value type of the entity. + ValueType types.ValueType_Enum `protobuf:"varint,2,opt,name=value_type,json=valueType,proto3,enum=feast.types.ValueType_Enum" json:"value_type,omitempty"` } -func (m *EntitySpec) Reset() { *m = EntitySpec{} } -func (m *EntitySpec) String() string { return proto.CompactTextString(m) } -func (*EntitySpec) ProtoMessage() {} -func (*EntitySpec) Descriptor() ([]byte, []int) { - return fileDescriptor_972fbd278ac19c0c, []int{2} +func (x *EntitySpec) Reset() { + *x = EntitySpec{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_FeatureSet_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *EntitySpec) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_EntitySpec.Unmarshal(m, b) -} -func (m *EntitySpec) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_EntitySpec.Marshal(b, m, deterministic) +func (x *EntitySpec) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *EntitySpec) XXX_Merge(src proto.Message) { - xxx_messageInfo_EntitySpec.Merge(m, src) -} -func (m *EntitySpec) XXX_Size() int { - return xxx_messageInfo_EntitySpec.Size(m) -} -func (m *EntitySpec) XXX_DiscardUnknown() { - xxx_messageInfo_EntitySpec.DiscardUnknown(m) + +func (*EntitySpec) ProtoMessage() {} + +func (x *EntitySpec) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_FeatureSet_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_EntitySpec proto.InternalMessageInfo +// Deprecated: Use EntitySpec.ProtoReflect.Descriptor instead. +func (*EntitySpec) Descriptor() ([]byte, []int) { + return file_feast_core_FeatureSet_proto_rawDescGZIP(), []int{2} +} -func (m *EntitySpec) GetName() string { - if m != nil { - return m.Name +func (x *EntitySpec) GetName() string { + if x != nil { + return x.Name } return "" } -func (m *EntitySpec) GetValueType() types.ValueType_Enum { - if m != nil { - return m.ValueType +func (x *EntitySpec) GetValueType() types.ValueType_Enum { + if x != nil { + return x.ValueType } return types.ValueType_INVALID } type FeatureSpec struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Name of the feature. Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // Value type of the feature. - ValueType types.ValueType_Enum `protobuf:"varint,2,opt,name=value_type,json=valueType,proto3,enum=feast.types.ValueType_Enum" json:"value_type,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + ValueType types.ValueType_Enum `protobuf:"varint,2,opt,name=value_type,json=valueType,proto3,enum=feast.types.ValueType_Enum" json:"value_type,omitempty"` + // Labels for user defined metadata on a feature + Labels map[string]string `protobuf:"bytes,16,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // Types that are assignable to PresenceConstraints: + // *FeatureSpec_Presence + // *FeatureSpec_GroupPresence + PresenceConstraints isFeatureSpec_PresenceConstraints `protobuf_oneof:"presence_constraints"` + // The shape of the feature which governs the number of values that appear in + // each example. + // + // Types that are assignable to ShapeType: + // *FeatureSpec_Shape + // *FeatureSpec_ValueCount + ShapeType isFeatureSpec_ShapeType `protobuf_oneof:"shape_type"` + // Domain for the values of the feature. + // + // Types that are assignable to DomainInfo: + // *FeatureSpec_Domain + // *FeatureSpec_IntDomain + // *FeatureSpec_FloatDomain + // *FeatureSpec_StringDomain + // *FeatureSpec_BoolDomain + // *FeatureSpec_StructDomain + // *FeatureSpec_NaturalLanguageDomain + // *FeatureSpec_ImageDomain + // *FeatureSpec_MidDomain + // *FeatureSpec_UrlDomain + // *FeatureSpec_TimeDomain + // *FeatureSpec_TimeOfDayDomain + DomainInfo isFeatureSpec_DomainInfo `protobuf_oneof:"domain_info"` +} + +func (x *FeatureSpec) Reset() { + *x = FeatureSpec{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_FeatureSet_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FeatureSpec) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *FeatureSpec) Reset() { *m = FeatureSpec{} } -func (m *FeatureSpec) String() string { return proto.CompactTextString(m) } -func (*FeatureSpec) ProtoMessage() {} +func (*FeatureSpec) ProtoMessage() {} + +func (x *FeatureSpec) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_FeatureSet_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FeatureSpec.ProtoReflect.Descriptor instead. func (*FeatureSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_972fbd278ac19c0c, []int{3} + return file_feast_core_FeatureSet_proto_rawDescGZIP(), []int{3} } -func (m *FeatureSpec) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_FeatureSpec.Unmarshal(m, b) +func (x *FeatureSpec) GetName() string { + if x != nil { + return x.Name + } + return "" } -func (m *FeatureSpec) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_FeatureSpec.Marshal(b, m, deterministic) + +func (x *FeatureSpec) GetValueType() types.ValueType_Enum { + if x != nil { + return x.ValueType + } + return types.ValueType_INVALID } -func (m *FeatureSpec) XXX_Merge(src proto.Message) { - xxx_messageInfo_FeatureSpec.Merge(m, src) + +func (x *FeatureSpec) GetLabels() map[string]string { + if x != nil { + return x.Labels + } + return nil } -func (m *FeatureSpec) XXX_Size() int { - return xxx_messageInfo_FeatureSpec.Size(m) + +func (m *FeatureSpec) GetPresenceConstraints() isFeatureSpec_PresenceConstraints { + if m != nil { + return m.PresenceConstraints + } + return nil } -func (m *FeatureSpec) XXX_DiscardUnknown() { - xxx_messageInfo_FeatureSpec.DiscardUnknown(m) + +func (x *FeatureSpec) GetPresence() *v0.FeaturePresence { + if x, ok := x.GetPresenceConstraints().(*FeatureSpec_Presence); ok { + return x.Presence + } + return nil } -var xxx_messageInfo_FeatureSpec proto.InternalMessageInfo +func (x *FeatureSpec) GetGroupPresence() *v0.FeaturePresenceWithinGroup { + if x, ok := x.GetPresenceConstraints().(*FeatureSpec_GroupPresence); ok { + return x.GroupPresence + } + return nil +} -func (m *FeatureSpec) GetName() string { +func (m *FeatureSpec) GetShapeType() isFeatureSpec_ShapeType { if m != nil { - return m.Name + return m.ShapeType } - return "" + return nil +} + +func (x *FeatureSpec) GetShape() *v0.FixedShape { + if x, ok := x.GetShapeType().(*FeatureSpec_Shape); ok { + return x.Shape + } + return nil } -func (m *FeatureSpec) GetValueType() types.ValueType_Enum { +func (x *FeatureSpec) GetValueCount() *v0.ValueCount { + if x, ok := x.GetShapeType().(*FeatureSpec_ValueCount); ok { + return x.ValueCount + } + return nil +} + +func (m *FeatureSpec) GetDomainInfo() isFeatureSpec_DomainInfo { if m != nil { - return m.ValueType + return m.DomainInfo } - return types.ValueType_INVALID + return nil +} + +func (x *FeatureSpec) GetDomain() string { + if x, ok := x.GetDomainInfo().(*FeatureSpec_Domain); ok { + return x.Domain + } + return "" +} + +func (x *FeatureSpec) GetIntDomain() *v0.IntDomain { + if x, ok := x.GetDomainInfo().(*FeatureSpec_IntDomain); ok { + return x.IntDomain + } + return nil } +func (x *FeatureSpec) GetFloatDomain() *v0.FloatDomain { + if x, ok := x.GetDomainInfo().(*FeatureSpec_FloatDomain); ok { + return x.FloatDomain + } + return nil +} + +func (x *FeatureSpec) GetStringDomain() *v0.StringDomain { + if x, ok := x.GetDomainInfo().(*FeatureSpec_StringDomain); ok { + return x.StringDomain + } + return nil +} + +func (x *FeatureSpec) GetBoolDomain() *v0.BoolDomain { + if x, ok := x.GetDomainInfo().(*FeatureSpec_BoolDomain); ok { + return x.BoolDomain + } + return nil +} + +func (x *FeatureSpec) GetStructDomain() *v0.StructDomain { + if x, ok := x.GetDomainInfo().(*FeatureSpec_StructDomain); ok { + return x.StructDomain + } + return nil +} + +func (x *FeatureSpec) GetNaturalLanguageDomain() *v0.NaturalLanguageDomain { + if x, ok := x.GetDomainInfo().(*FeatureSpec_NaturalLanguageDomain); ok { + return x.NaturalLanguageDomain + } + return nil +} + +func (x *FeatureSpec) GetImageDomain() *v0.ImageDomain { + if x, ok := x.GetDomainInfo().(*FeatureSpec_ImageDomain); ok { + return x.ImageDomain + } + return nil +} + +func (x *FeatureSpec) GetMidDomain() *v0.MIDDomain { + if x, ok := x.GetDomainInfo().(*FeatureSpec_MidDomain); ok { + return x.MidDomain + } + return nil +} + +func (x *FeatureSpec) GetUrlDomain() *v0.URLDomain { + if x, ok := x.GetDomainInfo().(*FeatureSpec_UrlDomain); ok { + return x.UrlDomain + } + return nil +} + +func (x *FeatureSpec) GetTimeDomain() *v0.TimeDomain { + if x, ok := x.GetDomainInfo().(*FeatureSpec_TimeDomain); ok { + return x.TimeDomain + } + return nil +} + +func (x *FeatureSpec) GetTimeOfDayDomain() *v0.TimeOfDayDomain { + if x, ok := x.GetDomainInfo().(*FeatureSpec_TimeOfDayDomain); ok { + return x.TimeOfDayDomain + } + return nil +} + +type isFeatureSpec_PresenceConstraints interface { + isFeatureSpec_PresenceConstraints() +} + +type FeatureSpec_Presence struct { + // Constraints on the presence of this feature in the examples. + Presence *v0.FeaturePresence `protobuf:"bytes,30,opt,name=presence,proto3,oneof"` +} + +type FeatureSpec_GroupPresence struct { + // Only used in the context of a "group" context, e.g., inside a sequence. + GroupPresence *v0.FeaturePresenceWithinGroup `protobuf:"bytes,31,opt,name=group_presence,json=groupPresence,proto3,oneof"` +} + +func (*FeatureSpec_Presence) isFeatureSpec_PresenceConstraints() {} + +func (*FeatureSpec_GroupPresence) isFeatureSpec_PresenceConstraints() {} + +type isFeatureSpec_ShapeType interface { + isFeatureSpec_ShapeType() +} + +type FeatureSpec_Shape struct { + // The feature has a fixed shape corresponding to a multi-dimensional + // tensor. + Shape *v0.FixedShape `protobuf:"bytes,32,opt,name=shape,proto3,oneof"` +} + +type FeatureSpec_ValueCount struct { + // The feature doesn't have a well defined shape. All we know are limits on + // the minimum and maximum number of values. + ValueCount *v0.ValueCount `protobuf:"bytes,33,opt,name=value_count,json=valueCount,proto3,oneof"` +} + +func (*FeatureSpec_Shape) isFeatureSpec_ShapeType() {} + +func (*FeatureSpec_ValueCount) isFeatureSpec_ShapeType() {} + +type isFeatureSpec_DomainInfo interface { + isFeatureSpec_DomainInfo() +} + +type FeatureSpec_Domain struct { + // Reference to a domain defined at the schema level. + Domain string `protobuf:"bytes,34,opt,name=domain,proto3,oneof"` +} + +type FeatureSpec_IntDomain struct { + // Inline definitions of domains. + IntDomain *v0.IntDomain `protobuf:"bytes,35,opt,name=int_domain,json=intDomain,proto3,oneof"` +} + +type FeatureSpec_FloatDomain struct { + FloatDomain *v0.FloatDomain `protobuf:"bytes,36,opt,name=float_domain,json=floatDomain,proto3,oneof"` +} + +type FeatureSpec_StringDomain struct { + StringDomain *v0.StringDomain `protobuf:"bytes,37,opt,name=string_domain,json=stringDomain,proto3,oneof"` +} + +type FeatureSpec_BoolDomain struct { + BoolDomain *v0.BoolDomain `protobuf:"bytes,38,opt,name=bool_domain,json=boolDomain,proto3,oneof"` +} + +type FeatureSpec_StructDomain struct { + StructDomain *v0.StructDomain `protobuf:"bytes,39,opt,name=struct_domain,json=structDomain,proto3,oneof"` +} + +type FeatureSpec_NaturalLanguageDomain struct { + // Supported semantic domains. + NaturalLanguageDomain *v0.NaturalLanguageDomain `protobuf:"bytes,40,opt,name=natural_language_domain,json=naturalLanguageDomain,proto3,oneof"` +} + +type FeatureSpec_ImageDomain struct { + ImageDomain *v0.ImageDomain `protobuf:"bytes,41,opt,name=image_domain,json=imageDomain,proto3,oneof"` +} + +type FeatureSpec_MidDomain struct { + MidDomain *v0.MIDDomain `protobuf:"bytes,42,opt,name=mid_domain,json=midDomain,proto3,oneof"` +} + +type FeatureSpec_UrlDomain struct { + UrlDomain *v0.URLDomain `protobuf:"bytes,43,opt,name=url_domain,json=urlDomain,proto3,oneof"` +} + +type FeatureSpec_TimeDomain struct { + TimeDomain *v0.TimeDomain `protobuf:"bytes,44,opt,name=time_domain,json=timeDomain,proto3,oneof"` +} + +type FeatureSpec_TimeOfDayDomain struct { + TimeOfDayDomain *v0.TimeOfDayDomain `protobuf:"bytes,45,opt,name=time_of_day_domain,json=timeOfDayDomain,proto3,oneof"` +} + +func (*FeatureSpec_Domain) isFeatureSpec_DomainInfo() {} + +func (*FeatureSpec_IntDomain) isFeatureSpec_DomainInfo() {} + +func (*FeatureSpec_FloatDomain) isFeatureSpec_DomainInfo() {} + +func (*FeatureSpec_StringDomain) isFeatureSpec_DomainInfo() {} + +func (*FeatureSpec_BoolDomain) isFeatureSpec_DomainInfo() {} + +func (*FeatureSpec_StructDomain) isFeatureSpec_DomainInfo() {} + +func (*FeatureSpec_NaturalLanguageDomain) isFeatureSpec_DomainInfo() {} + +func (*FeatureSpec_ImageDomain) isFeatureSpec_DomainInfo() {} + +func (*FeatureSpec_MidDomain) isFeatureSpec_DomainInfo() {} + +func (*FeatureSpec_UrlDomain) isFeatureSpec_DomainInfo() {} + +func (*FeatureSpec_TimeDomain) isFeatureSpec_DomainInfo() {} + +func (*FeatureSpec_TimeOfDayDomain) isFeatureSpec_DomainInfo() {} + type FeatureSetMeta struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Created timestamp of this specific feature set. CreatedTimestamp *timestamp.Timestamp `protobuf:"bytes,1,opt,name=created_timestamp,json=createdTimestamp,proto3" json:"created_timestamp,omitempty"` // Status of the feature set. @@ -307,94 +672,402 @@ type FeatureSetMeta struct { // 1) STATUS_PENDING - A feature set is in pending state if Feast has not spun up the jobs // necessary to push rows for this feature set to stores subscribing to this feature set. // 2) STATUS_READY - Feature set is ready for consumption or ingestion - Status FeatureSetStatus `protobuf:"varint,2,opt,name=status,proto3,enum=feast.core.FeatureSetStatus" json:"status,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Status FeatureSetStatus `protobuf:"varint,2,opt,name=status,proto3,enum=feast.core.FeatureSetStatus" json:"status,omitempty"` } -func (m *FeatureSetMeta) Reset() { *m = FeatureSetMeta{} } -func (m *FeatureSetMeta) String() string { return proto.CompactTextString(m) } -func (*FeatureSetMeta) ProtoMessage() {} -func (*FeatureSetMeta) Descriptor() ([]byte, []int) { - return fileDescriptor_972fbd278ac19c0c, []int{4} +func (x *FeatureSetMeta) Reset() { + *x = FeatureSetMeta{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_FeatureSet_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *FeatureSetMeta) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_FeatureSetMeta.Unmarshal(m, b) -} -func (m *FeatureSetMeta) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_FeatureSetMeta.Marshal(b, m, deterministic) -} -func (m *FeatureSetMeta) XXX_Merge(src proto.Message) { - xxx_messageInfo_FeatureSetMeta.Merge(m, src) +func (x *FeatureSetMeta) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *FeatureSetMeta) XXX_Size() int { - return xxx_messageInfo_FeatureSetMeta.Size(m) -} -func (m *FeatureSetMeta) XXX_DiscardUnknown() { - xxx_messageInfo_FeatureSetMeta.DiscardUnknown(m) + +func (*FeatureSetMeta) ProtoMessage() {} + +func (x *FeatureSetMeta) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_FeatureSet_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_FeatureSetMeta proto.InternalMessageInfo +// Deprecated: Use FeatureSetMeta.ProtoReflect.Descriptor instead. +func (*FeatureSetMeta) Descriptor() ([]byte, []int) { + return file_feast_core_FeatureSet_proto_rawDescGZIP(), []int{4} +} -func (m *FeatureSetMeta) GetCreatedTimestamp() *timestamp.Timestamp { - if m != nil { - return m.CreatedTimestamp +func (x *FeatureSetMeta) GetCreatedTimestamp() *timestamp.Timestamp { + if x != nil { + return x.CreatedTimestamp } return nil } -func (m *FeatureSetMeta) GetStatus() FeatureSetStatus { - if m != nil { - return m.Status +func (x *FeatureSetMeta) GetStatus() FeatureSetStatus { + if x != nil { + return x.Status } return FeatureSetStatus_STATUS_INVALID } -func init() { - proto.RegisterEnum("feast.core.FeatureSetStatus", FeatureSetStatus_name, FeatureSetStatus_value) - proto.RegisterType((*FeatureSet)(nil), "feast.core.FeatureSet") - proto.RegisterType((*FeatureSetSpec)(nil), "feast.core.FeatureSetSpec") - proto.RegisterType((*EntitySpec)(nil), "feast.core.EntitySpec") - proto.RegisterType((*FeatureSpec)(nil), "feast.core.FeatureSpec") - proto.RegisterType((*FeatureSetMeta)(nil), "feast.core.FeatureSetMeta") -} - -func init() { proto.RegisterFile("feast/core/FeatureSet.proto", fileDescriptor_972fbd278ac19c0c) } - -var fileDescriptor_972fbd278ac19c0c = []byte{ - // 510 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x93, 0x4f, 0x6f, 0xda, 0x30, - 0x18, 0xc6, 0x07, 0xa5, 0x50, 0x5e, 0x26, 0x96, 0xf9, 0xb0, 0x66, 0xed, 0xb4, 0x21, 0x4e, 0xa8, - 0x07, 0x5b, 0x4a, 0x77, 0xda, 0x8d, 0x0a, 0x56, 0x21, 0x75, 0xa8, 0x72, 0x58, 0xa5, 0x4d, 0x9b, - 0x90, 0x09, 0x2f, 0x59, 0x5a, 0x82, 0xa3, 0xd8, 0x41, 0xe5, 0x53, 0xec, 0x33, 0xec, 0x9b, 0x4e, - 0x71, 0x12, 0x92, 0xa1, 0x6e, 0xa7, 0xdd, 0x62, 0x3f, 0x3f, 0xbf, 0x79, 0xde, 0x7f, 0x70, 0xbe, - 0x42, 0xa1, 0x34, 0xf3, 0x64, 0x8c, 0xec, 0x23, 0x0a, 0x9d, 0xc4, 0xe8, 0xa2, 0xa6, 0x51, 0x2c, - 0xb5, 0x24, 0x60, 0x44, 0x9a, 0x8a, 0x67, 0xa7, 0x19, 0xa8, 0x77, 0x11, 0x2a, 0x76, 0x27, 0xd6, - 0x09, 0x66, 0x50, 0x21, 0x98, 0x08, 0xae, 0x4c, 0x62, 0xaf, 0x10, 0xde, 0xfa, 0x52, 0xfa, 0x6b, - 0x64, 0xe6, 0xb4, 0x48, 0x56, 0x6c, 0x99, 0xc4, 0x42, 0x07, 0x72, 0x93, 0xeb, 0xef, 0x0e, 0x75, - 0x1d, 0x84, 0xa8, 0xb4, 0x08, 0xa3, 0x0c, 0xe8, 0xaf, 0x01, 0x4a, 0x4b, 0x84, 0x42, 0x43, 0x45, - 0xe8, 0xd9, 0xb5, 0x5e, 0x6d, 0xd0, 0x71, 0xce, 0x68, 0xe9, 0x8d, 0x96, 0x94, 0x1b, 0xa1, 0xc7, - 0x0d, 0x97, 0xf2, 0x21, 0x6a, 0x61, 0xd7, 0xff, 0xc5, 0x7f, 0x42, 0x2d, 0xb8, 0xe1, 0xfa, 0xbf, - 0xea, 0xd0, 0xfd, 0x33, 0x10, 0xb1, 0xa1, 0x15, 0xc5, 0xf2, 0x1e, 0x3d, 0x6d, 0xb7, 0x7a, 0xb5, - 0x41, 0x9b, 0x17, 0x47, 0x42, 0xa0, 0xb1, 0x11, 0x21, 0x1a, 0x33, 0x6d, 0x6e, 0xbe, 0x53, 0x7a, - 0x8b, 0xb1, 0x0a, 0xe4, 0xc6, 0xfc, 0xf3, 0x98, 0x17, 0x47, 0xe2, 0xc0, 0x09, 0x6e, 0x74, 0xa0, - 0x03, 0x54, 0xf6, 0x51, 0xef, 0x68, 0xd0, 0x71, 0x5e, 0x55, 0xed, 0x8c, 0x53, 0x6d, 0x67, 0xac, - 0xef, 0x39, 0x72, 0x09, 0x27, 0xab, 0xcc, 0x8d, 0xb2, 0x1b, 0xe6, 0xcd, 0xe9, 0x53, 0x29, 0x98, - 0x47, 0x05, 0x48, 0x1c, 0x68, 0x85, 0xe2, 0x71, 0x2e, 0x7c, 0xb4, 0x8f, 0x4d, 0xda, 0xaf, 0x69, - 0x56, 0x64, 0x5a, 0x14, 0x99, 0x8e, 0xf2, 0x26, 0xf0, 0x66, 0x28, 0x1e, 0x87, 0x3e, 0x92, 0x0b, - 0x68, 0x2a, 0xd3, 0x36, 0xbb, 0x69, 0x9e, 0x90, 0xea, 0x6f, 0xb2, 0x86, 0xf2, 0x9c, 0xe8, 0x7f, - 0x03, 0x28, 0xcd, 0x3e, 0x59, 0x84, 0x0f, 0x00, 0xdb, 0x74, 0x38, 0xe6, 0xe9, 0xa0, 0x98, 0x3a, - 0x74, 0x9d, 0xf3, 0x3c, 0xa2, 0x99, 0x1d, 0x6a, 0x66, 0x67, 0xb6, 0x8b, 0xd2, 0xbc, 0x93, 0x90, - 0xb7, 0xb7, 0xc5, 0xb9, 0xff, 0x1d, 0x3a, 0x95, 0xb4, 0xfe, 0x7b, 0xf8, 0x9f, 0xb5, 0x6a, 0x83, - 0xd3, 0xce, 0x93, 0x6b, 0x78, 0xe9, 0xc5, 0x28, 0x34, 0x2e, 0xe7, 0xfb, 0xe1, 0xdb, 0x0f, 0xd8, - 0x61, 0xe5, 0x66, 0x05, 0xc1, 0xad, 0xfc, 0xd1, 0xfe, 0x86, 0xbc, 0x87, 0xa6, 0xd2, 0x42, 0x27, - 0x2a, 0xf7, 0xf4, 0xe6, 0x2f, 0xe3, 0x69, 0x18, 0x9e, 0xb3, 0x17, 0x37, 0x60, 0x1d, 0x6a, 0x84, - 0x40, 0xd7, 0x9d, 0x0d, 0x67, 0x9f, 0xdd, 0xf9, 0x64, 0x7a, 0x37, 0xbc, 0x99, 0x8c, 0xac, 0x67, - 0x95, 0xbb, 0xdb, 0xf1, 0x74, 0x34, 0x99, 0x5e, 0x5b, 0x35, 0x62, 0xc1, 0xf3, 0xfc, 0x8e, 0x8f, - 0x87, 0xa3, 0x2f, 0x56, 0xfd, 0x6a, 0x0a, 0x95, 0x7d, 0xbd, 0x7a, 0x51, 0x46, 0xbe, 0x4d, 0x33, - 0xf8, 0xca, 0xfc, 0x40, 0xff, 0x48, 0x16, 0xd4, 0x93, 0x21, 0xf3, 0xe5, 0x3d, 0x3e, 0xb0, 0x6c, - 0x71, 0xd5, 0xf2, 0x81, 0xf9, 0x32, 0xdb, 0x42, 0xc5, 0xca, 0x65, 0x5e, 0x34, 0xcd, 0xd5, 0xe5, - 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0xb8, 0xd5, 0xf0, 0x13, 0x23, 0x04, 0x00, 0x00, +var File_feast_core_FeatureSet_proto protoreflect.FileDescriptor + +var file_feast_core_FeatureSet_proto_rawDesc = []byte{ + 0x0a, 0x1b, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x46, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x66, + 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x1a, 0x17, 0x66, 0x65, 0x61, 0x73, 0x74, + 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x1a, 0x17, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x53, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x29, 0x74, 0x65, + 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x30, 0x2f, 0x73, 0x63, 0x68, 0x65, 0x6d, + 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x6c, 0x0a, 0x0a, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x53, 0x65, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, + 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x53, 0x70, 0x65, 0x63, 0x52, + 0x04, 0x73, 0x70, 0x65, 0x63, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, + 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, + 0x04, 0x6d, 0x65, 0x74, 0x61, 0x22, 0x88, 0x03, 0x0a, 0x0e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x53, 0x65, 0x74, 0x53, 0x70, 0x65, 0x63, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x6a, + 0x65, 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, + 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x32, 0x0a, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, + 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, + 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x70, 0x65, 0x63, + 0x52, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x33, 0x0a, 0x08, 0x66, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x66, + 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x53, 0x70, 0x65, 0x63, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, + 0x32, 0x0a, 0x07, 0x6d, 0x61, 0x78, 0x5f, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x6d, 0x61, 0x78, + 0x41, 0x67, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, + 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, + 0x3e, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x26, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x46, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x53, 0x70, 0x65, 0x63, 0x2e, 0x4c, 0x61, 0x62, 0x65, + 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x1a, + 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, + 0x22, 0x5c, 0x0a, 0x0a, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x70, 0x65, 0x63, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x3a, 0x0a, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x54, 0x79, 0x70, 0x65, 0x2e, 0x45, + 0x6e, 0x75, 0x6d, 0x52, 0x09, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0xa0, + 0x0b, 0x0a, 0x0b, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x70, 0x65, 0x63, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x3a, 0x0a, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x54, 0x79, 0x70, 0x65, 0x2e, 0x45, + 0x6e, 0x75, 0x6d, 0x52, 0x09, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x3b, + 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x10, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, + 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x53, 0x70, 0x65, 0x63, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x45, 0x0a, 0x08, 0x70, + 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, + 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x50, 0x72, + 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x48, 0x00, 0x52, 0x08, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, + 0x63, 0x65, 0x12, 0x5b, 0x0a, 0x0e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x70, 0x72, 0x65, 0x73, + 0x65, 0x6e, 0x63, 0x65, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x74, 0x65, 0x6e, + 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x2e, 0x76, 0x30, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x50, 0x72, 0x65, 0x73, 0x65, + 0x6e, 0x63, 0x65, 0x57, 0x69, 0x74, 0x68, 0x69, 0x6e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x48, 0x00, + 0x52, 0x0d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x12, + 0x3a, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x70, 0x65, 0x18, 0x20, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, + 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x46, 0x69, 0x78, 0x65, 0x64, 0x53, 0x68, 0x61, + 0x70, 0x65, 0x48, 0x01, 0x52, 0x05, 0x73, 0x68, 0x61, 0x70, 0x65, 0x12, 0x45, 0x0a, 0x0b, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x21, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x48, 0x01, 0x52, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x22, 0x20, 0x01, + 0x28, 0x09, 0x48, 0x02, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x42, 0x0a, 0x0a, + 0x69, 0x6e, 0x74, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x23, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x21, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x49, 0x6e, 0x74, 0x44, 0x6f, 0x6d, + 0x61, 0x69, 0x6e, 0x48, 0x02, 0x52, 0x09, 0x69, 0x6e, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + 0x12, 0x48, 0x0a, 0x0c, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + 0x18, 0x24, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, + 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, + 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x48, 0x02, 0x52, 0x0b, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x4b, 0x0a, 0x0d, 0x73, 0x74, + 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x25, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x24, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x48, 0x02, 0x52, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x45, 0x0a, 0x0b, 0x62, 0x6f, 0x6f, 0x6c, 0x5f, + 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x26, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, + 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + 0x48, 0x02, 0x52, 0x0a, 0x62, 0x6f, 0x6f, 0x6c, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x4b, + 0x0a, 0x0d, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, + 0x27, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, + 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x53, + 0x74, 0x72, 0x75, 0x63, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x48, 0x02, 0x52, 0x0c, 0x73, + 0x74, 0x72, 0x75, 0x63, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x67, 0x0a, 0x17, 0x6e, + 0x61, 0x74, 0x75, 0x72, 0x61, 0x6c, 0x5f, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x5f, + 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x74, + 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x4e, 0x61, 0x74, 0x75, 0x72, 0x61, 0x6c, 0x4c, 0x61, 0x6e, + 0x67, 0x75, 0x61, 0x67, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x48, 0x02, 0x52, 0x15, 0x6e, + 0x61, 0x74, 0x75, 0x72, 0x61, 0x6c, 0x4c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x44, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x48, 0x0a, 0x0c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x64, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x29, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x74, 0x65, 0x6e, + 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x2e, 0x76, 0x30, 0x2e, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x48, + 0x02, 0x52, 0x0b, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x42, + 0x0a, 0x0a, 0x6d, 0x69, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x2a, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x4d, 0x49, 0x44, 0x44, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x48, 0x02, 0x52, 0x09, 0x6d, 0x69, 0x64, 0x44, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x12, 0x42, 0x0a, 0x0a, 0x75, 0x72, 0x6c, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + 0x18, 0x2b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, + 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, + 0x55, 0x52, 0x4c, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x48, 0x02, 0x52, 0x09, 0x75, 0x72, 0x6c, + 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x45, 0x0a, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x64, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x2c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, + 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x48, + 0x02, 0x52, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x56, 0x0a, + 0x12, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6f, 0x66, 0x5f, 0x64, 0x61, 0x79, 0x5f, 0x64, 0x6f, 0x6d, + 0x61, 0x69, 0x6e, 0x18, 0x2d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x74, 0x65, 0x6e, 0x73, + 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, + 0x76, 0x30, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x4f, 0x66, 0x44, 0x61, 0x79, 0x44, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x48, 0x02, 0x52, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x4f, 0x66, 0x44, 0x61, 0x79, 0x44, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x42, 0x16, 0x0a, 0x14, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x6e, + 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73, 0x42, 0x0c, 0x0a, 0x0a, 0x73, 0x68, 0x61, 0x70, + 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x42, 0x0d, 0x0a, 0x0b, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x10, 0x4a, 0x04, 0x08, 0x11, 0x10, + 0x1e, 0x22, 0x8f, 0x01, 0x0a, 0x0e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, + 0x4d, 0x65, 0x74, 0x61, 0x12, 0x47, 0x0a, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, + 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x34, 0x0a, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, + 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x53, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x2a, 0x4c, 0x0a, 0x10, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, + 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, + 0x53, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x53, + 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, + 0x10, 0x0a, 0x0c, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x52, 0x45, 0x41, 0x44, 0x59, 0x10, + 0x02, 0x42, 0x58, 0x0a, 0x10, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x42, 0x0f, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, + 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x5a, 0x33, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x66, 0x65, 0x61, + 0x73, 0x74, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, + 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, +} + +var ( + file_feast_core_FeatureSet_proto_rawDescOnce sync.Once + file_feast_core_FeatureSet_proto_rawDescData = file_feast_core_FeatureSet_proto_rawDesc +) + +func file_feast_core_FeatureSet_proto_rawDescGZIP() []byte { + file_feast_core_FeatureSet_proto_rawDescOnce.Do(func() { + file_feast_core_FeatureSet_proto_rawDescData = protoimpl.X.CompressGZIP(file_feast_core_FeatureSet_proto_rawDescData) + }) + return file_feast_core_FeatureSet_proto_rawDescData +} + +var file_feast_core_FeatureSet_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_feast_core_FeatureSet_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_feast_core_FeatureSet_proto_goTypes = []interface{}{ + (FeatureSetStatus)(0), // 0: feast.core.FeatureSetStatus + (*FeatureSet)(nil), // 1: feast.core.FeatureSet + (*FeatureSetSpec)(nil), // 2: feast.core.FeatureSetSpec + (*EntitySpec)(nil), // 3: feast.core.EntitySpec + (*FeatureSpec)(nil), // 4: feast.core.FeatureSpec + (*FeatureSetMeta)(nil), // 5: feast.core.FeatureSetMeta + nil, // 6: feast.core.FeatureSetSpec.LabelsEntry + nil, // 7: feast.core.FeatureSpec.LabelsEntry + (*duration.Duration)(nil), // 8: google.protobuf.Duration + (*Source)(nil), // 9: feast.core.Source + (types.ValueType_Enum)(0), // 10: feast.types.ValueType.Enum + (*v0.FeaturePresence)(nil), // 11: tensorflow.metadata.v0.FeaturePresence + (*v0.FeaturePresenceWithinGroup)(nil), // 12: tensorflow.metadata.v0.FeaturePresenceWithinGroup + (*v0.FixedShape)(nil), // 13: tensorflow.metadata.v0.FixedShape + (*v0.ValueCount)(nil), // 14: tensorflow.metadata.v0.ValueCount + (*v0.IntDomain)(nil), // 15: tensorflow.metadata.v0.IntDomain + (*v0.FloatDomain)(nil), // 16: tensorflow.metadata.v0.FloatDomain + (*v0.StringDomain)(nil), // 17: tensorflow.metadata.v0.StringDomain + (*v0.BoolDomain)(nil), // 18: tensorflow.metadata.v0.BoolDomain + (*v0.StructDomain)(nil), // 19: tensorflow.metadata.v0.StructDomain + (*v0.NaturalLanguageDomain)(nil), // 20: tensorflow.metadata.v0.NaturalLanguageDomain + (*v0.ImageDomain)(nil), // 21: tensorflow.metadata.v0.ImageDomain + (*v0.MIDDomain)(nil), // 22: tensorflow.metadata.v0.MIDDomain + (*v0.URLDomain)(nil), // 23: tensorflow.metadata.v0.URLDomain + (*v0.TimeDomain)(nil), // 24: tensorflow.metadata.v0.TimeDomain + (*v0.TimeOfDayDomain)(nil), // 25: tensorflow.metadata.v0.TimeOfDayDomain + (*timestamp.Timestamp)(nil), // 26: google.protobuf.Timestamp +} +var file_feast_core_FeatureSet_proto_depIdxs = []int32{ + 2, // 0: feast.core.FeatureSet.spec:type_name -> feast.core.FeatureSetSpec + 5, // 1: feast.core.FeatureSet.meta:type_name -> feast.core.FeatureSetMeta + 3, // 2: feast.core.FeatureSetSpec.entities:type_name -> feast.core.EntitySpec + 4, // 3: feast.core.FeatureSetSpec.features:type_name -> feast.core.FeatureSpec + 8, // 4: feast.core.FeatureSetSpec.max_age:type_name -> google.protobuf.Duration + 9, // 5: feast.core.FeatureSetSpec.source:type_name -> feast.core.Source + 6, // 6: feast.core.FeatureSetSpec.labels:type_name -> feast.core.FeatureSetSpec.LabelsEntry + 10, // 7: feast.core.EntitySpec.value_type:type_name -> feast.types.ValueType.Enum + 10, // 8: feast.core.FeatureSpec.value_type:type_name -> feast.types.ValueType.Enum + 7, // 9: feast.core.FeatureSpec.labels:type_name -> feast.core.FeatureSpec.LabelsEntry + 11, // 10: feast.core.FeatureSpec.presence:type_name -> tensorflow.metadata.v0.FeaturePresence + 12, // 11: feast.core.FeatureSpec.group_presence:type_name -> tensorflow.metadata.v0.FeaturePresenceWithinGroup + 13, // 12: feast.core.FeatureSpec.shape:type_name -> tensorflow.metadata.v0.FixedShape + 14, // 13: feast.core.FeatureSpec.value_count:type_name -> tensorflow.metadata.v0.ValueCount + 15, // 14: feast.core.FeatureSpec.int_domain:type_name -> tensorflow.metadata.v0.IntDomain + 16, // 15: feast.core.FeatureSpec.float_domain:type_name -> tensorflow.metadata.v0.FloatDomain + 17, // 16: feast.core.FeatureSpec.string_domain:type_name -> tensorflow.metadata.v0.StringDomain + 18, // 17: feast.core.FeatureSpec.bool_domain:type_name -> tensorflow.metadata.v0.BoolDomain + 19, // 18: feast.core.FeatureSpec.struct_domain:type_name -> tensorflow.metadata.v0.StructDomain + 20, // 19: feast.core.FeatureSpec.natural_language_domain:type_name -> tensorflow.metadata.v0.NaturalLanguageDomain + 21, // 20: feast.core.FeatureSpec.image_domain:type_name -> tensorflow.metadata.v0.ImageDomain + 22, // 21: feast.core.FeatureSpec.mid_domain:type_name -> tensorflow.metadata.v0.MIDDomain + 23, // 22: feast.core.FeatureSpec.url_domain:type_name -> tensorflow.metadata.v0.URLDomain + 24, // 23: feast.core.FeatureSpec.time_domain:type_name -> tensorflow.metadata.v0.TimeDomain + 25, // 24: feast.core.FeatureSpec.time_of_day_domain:type_name -> tensorflow.metadata.v0.TimeOfDayDomain + 26, // 25: feast.core.FeatureSetMeta.created_timestamp:type_name -> google.protobuf.Timestamp + 0, // 26: feast.core.FeatureSetMeta.status:type_name -> feast.core.FeatureSetStatus + 27, // [27:27] is the sub-list for method output_type + 27, // [27:27] is the sub-list for method input_type + 27, // [27:27] is the sub-list for extension type_name + 27, // [27:27] is the sub-list for extension extendee + 0, // [0:27] is the sub-list for field type_name +} + +func init() { file_feast_core_FeatureSet_proto_init() } +func file_feast_core_FeatureSet_proto_init() { + if File_feast_core_FeatureSet_proto != nil { + return + } + file_feast_core_Source_proto_init() + if !protoimpl.UnsafeEnabled { + file_feast_core_FeatureSet_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FeatureSet); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_FeatureSet_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FeatureSetSpec); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_FeatureSet_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*EntitySpec); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_FeatureSet_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FeatureSpec); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_FeatureSet_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FeatureSetMeta); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_feast_core_FeatureSet_proto_msgTypes[3].OneofWrappers = []interface{}{ + (*FeatureSpec_Presence)(nil), + (*FeatureSpec_GroupPresence)(nil), + (*FeatureSpec_Shape)(nil), + (*FeatureSpec_ValueCount)(nil), + (*FeatureSpec_Domain)(nil), + (*FeatureSpec_IntDomain)(nil), + (*FeatureSpec_FloatDomain)(nil), + (*FeatureSpec_StringDomain)(nil), + (*FeatureSpec_BoolDomain)(nil), + (*FeatureSpec_StructDomain)(nil), + (*FeatureSpec_NaturalLanguageDomain)(nil), + (*FeatureSpec_ImageDomain)(nil), + (*FeatureSpec_MidDomain)(nil), + (*FeatureSpec_UrlDomain)(nil), + (*FeatureSpec_TimeDomain)(nil), + (*FeatureSpec_TimeOfDayDomain)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_feast_core_FeatureSet_proto_rawDesc, + NumEnums: 1, + NumMessages: 7, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_feast_core_FeatureSet_proto_goTypes, + DependencyIndexes: file_feast_core_FeatureSet_proto_depIdxs, + EnumInfos: file_feast_core_FeatureSet_proto_enumTypes, + MessageInfos: file_feast_core_FeatureSet_proto_msgTypes, + }.Build() + File_feast_core_FeatureSet_proto = out.File + file_feast_core_FeatureSet_proto_rawDesc = nil + file_feast_core_FeatureSet_proto_goTypes = nil + file_feast_core_FeatureSet_proto_depIdxs = nil } diff --git a/sdk/go/protos/feast/core/FeatureSetReference.pb.go b/sdk/go/protos/feast/core/FeatureSetReference.pb.go new file mode 100644 index 00000000000..3da39ad922a --- /dev/null +++ b/sdk/go/protos/feast/core/FeatureSetReference.pb.go @@ -0,0 +1,183 @@ +// +// Copyright 2020 The Feast Authors +// +// 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.23.0 +// protoc v3.10.0 +// source: feast/core/FeatureSetReference.proto + +package core + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +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 that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +// Defines a composite key that refers to a unique FeatureSet +type FeatureSetReference struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Name of the project + Project string `protobuf:"bytes,1,opt,name=project,proto3" json:"project,omitempty"` + // Name of the FeatureSet + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *FeatureSetReference) Reset() { + *x = FeatureSetReference{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_FeatureSetReference_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FeatureSetReference) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FeatureSetReference) ProtoMessage() {} + +func (x *FeatureSetReference) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_FeatureSetReference_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FeatureSetReference.ProtoReflect.Descriptor instead. +func (*FeatureSetReference) Descriptor() ([]byte, []int) { + return file_feast_core_FeatureSetReference_proto_rawDescGZIP(), []int{0} +} + +func (x *FeatureSetReference) GetProject() string { + if x != nil { + return x.Project + } + return "" +} + +func (x *FeatureSetReference) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +var File_feast_core_FeatureSetReference_proto protoreflect.FileDescriptor + +var file_feast_core_FeatureSetReference_proto_rawDesc = []byte{ + 0x0a, 0x24, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x46, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x22, 0x49, 0x0a, 0x13, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, + 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, + 0x6a, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x6a, + 0x65, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x42, 0x61, 0x0a, + 0x10, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x72, + 0x65, 0x42, 0x18, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x66, + 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x5a, 0x33, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2d, 0x64, 0x65, + 0x76, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x67, 0x6f, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x63, 0x6f, 0x72, 0x65, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_feast_core_FeatureSetReference_proto_rawDescOnce sync.Once + file_feast_core_FeatureSetReference_proto_rawDescData = file_feast_core_FeatureSetReference_proto_rawDesc +) + +func file_feast_core_FeatureSetReference_proto_rawDescGZIP() []byte { + file_feast_core_FeatureSetReference_proto_rawDescOnce.Do(func() { + file_feast_core_FeatureSetReference_proto_rawDescData = protoimpl.X.CompressGZIP(file_feast_core_FeatureSetReference_proto_rawDescData) + }) + return file_feast_core_FeatureSetReference_proto_rawDescData +} + +var file_feast_core_FeatureSetReference_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_feast_core_FeatureSetReference_proto_goTypes = []interface{}{ + (*FeatureSetReference)(nil), // 0: feast.core.FeatureSetReference +} +var file_feast_core_FeatureSetReference_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_feast_core_FeatureSetReference_proto_init() } +func file_feast_core_FeatureSetReference_proto_init() { + if File_feast_core_FeatureSetReference_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_feast_core_FeatureSetReference_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FeatureSetReference); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_feast_core_FeatureSetReference_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_feast_core_FeatureSetReference_proto_goTypes, + DependencyIndexes: file_feast_core_FeatureSetReference_proto_depIdxs, + MessageInfos: file_feast_core_FeatureSetReference_proto_msgTypes, + }.Build() + File_feast_core_FeatureSetReference_proto = out.File + file_feast_core_FeatureSetReference_proto_rawDesc = nil + file_feast_core_FeatureSetReference_proto_goTypes = nil + file_feast_core_FeatureSetReference_proto_depIdxs = nil +} diff --git a/sdk/go/protos/feast/core/IngestionJob.pb.go b/sdk/go/protos/feast/core/IngestionJob.pb.go new file mode 100644 index 00000000000..bf30f25168f --- /dev/null +++ b/sdk/go/protos/feast/core/IngestionJob.pb.go @@ -0,0 +1,334 @@ +// +// Copyright 2020 The Feast Authors +// +// 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.23.0 +// protoc v3.10.0 +// source: feast/core/IngestionJob.proto + +package core + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +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 that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +// Status of a Feast Ingestion Job +type IngestionJobStatus int32 + +const ( + // Job status is not known. + IngestionJobStatus_UNKNOWN IngestionJobStatus = 0 + // Import job is submitted to runner and currently pending for executing + IngestionJobStatus_PENDING IngestionJobStatus = 1 + // Import job is currently running in the runner + IngestionJobStatus_RUNNING IngestionJobStatus = 2 + // Runner's reported the import job has completed (applicable to batch job) + IngestionJobStatus_COMPLETED IngestionJobStatus = 3 + // When user sent abort command, but it's still running + IngestionJobStatus_ABORTING IngestionJobStatus = 4 + // User initiated abort job + IngestionJobStatus_ABORTED IngestionJobStatus = 5 + // Runner's reported that the import job failed to run or there is a failure during job + IngestionJobStatus_ERROR IngestionJobStatus = 6 + // job has been suspended and waiting for cleanup + IngestionJobStatus_SUSPENDING IngestionJobStatus = 7 + // job has been suspended + IngestionJobStatus_SUSPENDED IngestionJobStatus = 8 +) + +// Enum value maps for IngestionJobStatus. +var ( + IngestionJobStatus_name = map[int32]string{ + 0: "UNKNOWN", + 1: "PENDING", + 2: "RUNNING", + 3: "COMPLETED", + 4: "ABORTING", + 5: "ABORTED", + 6: "ERROR", + 7: "SUSPENDING", + 8: "SUSPENDED", + } + IngestionJobStatus_value = map[string]int32{ + "UNKNOWN": 0, + "PENDING": 1, + "RUNNING": 2, + "COMPLETED": 3, + "ABORTING": 4, + "ABORTED": 5, + "ERROR": 6, + "SUSPENDING": 7, + "SUSPENDED": 8, + } +) + +func (x IngestionJobStatus) Enum() *IngestionJobStatus { + p := new(IngestionJobStatus) + *p = x + return p +} + +func (x IngestionJobStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (IngestionJobStatus) Descriptor() protoreflect.EnumDescriptor { + return file_feast_core_IngestionJob_proto_enumTypes[0].Descriptor() +} + +func (IngestionJobStatus) Type() protoreflect.EnumType { + return &file_feast_core_IngestionJob_proto_enumTypes[0] +} + +func (x IngestionJobStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use IngestionJobStatus.Descriptor instead. +func (IngestionJobStatus) EnumDescriptor() ([]byte, []int) { + return file_feast_core_IngestionJob_proto_rawDescGZIP(), []int{0} +} + +// Represents Feast Injestion Job +type IngestionJob struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Job ID assigned by Feast + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // External job ID specific to the runner. + // For DirectRunner jobs, this is identical to id. For DataflowRunner jobs, this refers to the Dataflow job ID. + ExternalId string `protobuf:"bytes,2,opt,name=external_id,json=externalId,proto3" json:"external_id,omitempty"` + Status IngestionJobStatus `protobuf:"varint,3,opt,name=status,proto3,enum=feast.core.IngestionJobStatus" json:"status,omitempty"` + // List of feature sets whose features are populated by this job. + FeatureSets []*FeatureSet `protobuf:"bytes,4,rep,name=feature_sets,json=featureSets,proto3" json:"feature_sets,omitempty"` + // Source this job is reading from. + Source *Source `protobuf:"bytes,5,opt,name=source,proto3" json:"source,omitempty"` + // Store this job is writing to. + Store *Store `protobuf:"bytes,6,opt,name=store,proto3" json:"store,omitempty"` +} + +func (x *IngestionJob) Reset() { + *x = IngestionJob{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_IngestionJob_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *IngestionJob) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IngestionJob) ProtoMessage() {} + +func (x *IngestionJob) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_IngestionJob_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IngestionJob.ProtoReflect.Descriptor instead. +func (*IngestionJob) Descriptor() ([]byte, []int) { + return file_feast_core_IngestionJob_proto_rawDescGZIP(), []int{0} +} + +func (x *IngestionJob) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *IngestionJob) GetExternalId() string { + if x != nil { + return x.ExternalId + } + return "" +} + +func (x *IngestionJob) GetStatus() IngestionJobStatus { + if x != nil { + return x.Status + } + return IngestionJobStatus_UNKNOWN +} + +func (x *IngestionJob) GetFeatureSets() []*FeatureSet { + if x != nil { + return x.FeatureSets + } + return nil +} + +func (x *IngestionJob) GetSource() *Source { + if x != nil { + return x.Source + } + return nil +} + +func (x *IngestionJob) GetStore() *Store { + if x != nil { + return x.Store + } + return nil +} + +var File_feast_core_IngestionJob_proto protoreflect.FileDescriptor + +var file_feast_core_IngestionJob_proto_rawDesc = []byte{ + 0x0a, 0x1d, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x49, 0x6e, 0x67, + 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x0a, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x1a, 0x1b, 0x66, 0x65, 0x61, + 0x73, 0x74, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, + 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x16, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, + 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x1a, 0x17, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x53, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x87, 0x02, 0x0a, 0x0c, 0x49, 0x6e, + 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x36, 0x0a, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x66, 0x65, + 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, + 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x39, 0x0a, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, + 0x65, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x66, 0x65, 0x61, 0x73, + 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, + 0x74, 0x52, 0x0b, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x73, 0x12, 0x2a, + 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, + 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x27, 0x0a, 0x05, 0x73, 0x74, + 0x6f, 0x72, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x66, 0x65, 0x61, 0x73, + 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x05, 0x73, 0x74, + 0x6f, 0x72, 0x65, 0x2a, 0x8f, 0x01, 0x0a, 0x12, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, + 0x6e, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, + 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x45, 0x4e, 0x44, 0x49, + 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x55, 0x4e, 0x4e, 0x49, 0x4e, 0x47, 0x10, + 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x03, + 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x04, 0x12, 0x0b, + 0x0a, 0x07, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x10, 0x05, 0x12, 0x09, 0x0a, 0x05, 0x45, + 0x52, 0x52, 0x4f, 0x52, 0x10, 0x06, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x55, 0x53, 0x50, 0x45, 0x4e, + 0x44, 0x49, 0x4e, 0x47, 0x10, 0x07, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x55, 0x53, 0x50, 0x45, 0x4e, + 0x44, 0x45, 0x44, 0x10, 0x08, 0x42, 0x5a, 0x0a, 0x10, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x42, 0x11, 0x49, 0x6e, 0x67, 0x65, 0x73, + 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x5a, 0x33, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2d, 0x64, + 0x65, 0x76, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x67, 0x6f, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x63, 0x6f, 0x72, + 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_feast_core_IngestionJob_proto_rawDescOnce sync.Once + file_feast_core_IngestionJob_proto_rawDescData = file_feast_core_IngestionJob_proto_rawDesc +) + +func file_feast_core_IngestionJob_proto_rawDescGZIP() []byte { + file_feast_core_IngestionJob_proto_rawDescOnce.Do(func() { + file_feast_core_IngestionJob_proto_rawDescData = protoimpl.X.CompressGZIP(file_feast_core_IngestionJob_proto_rawDescData) + }) + return file_feast_core_IngestionJob_proto_rawDescData +} + +var file_feast_core_IngestionJob_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_feast_core_IngestionJob_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_feast_core_IngestionJob_proto_goTypes = []interface{}{ + (IngestionJobStatus)(0), // 0: feast.core.IngestionJobStatus + (*IngestionJob)(nil), // 1: feast.core.IngestionJob + (*FeatureSet)(nil), // 2: feast.core.FeatureSet + (*Source)(nil), // 3: feast.core.Source + (*Store)(nil), // 4: feast.core.Store +} +var file_feast_core_IngestionJob_proto_depIdxs = []int32{ + 0, // 0: feast.core.IngestionJob.status:type_name -> feast.core.IngestionJobStatus + 2, // 1: feast.core.IngestionJob.feature_sets:type_name -> feast.core.FeatureSet + 3, // 2: feast.core.IngestionJob.source:type_name -> feast.core.Source + 4, // 3: feast.core.IngestionJob.store:type_name -> feast.core.Store + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_feast_core_IngestionJob_proto_init() } +func file_feast_core_IngestionJob_proto_init() { + if File_feast_core_IngestionJob_proto != nil { + return + } + file_feast_core_FeatureSet_proto_init() + file_feast_core_Store_proto_init() + file_feast_core_Source_proto_init() + if !protoimpl.UnsafeEnabled { + file_feast_core_IngestionJob_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*IngestionJob); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_feast_core_IngestionJob_proto_rawDesc, + NumEnums: 1, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_feast_core_IngestionJob_proto_goTypes, + DependencyIndexes: file_feast_core_IngestionJob_proto_depIdxs, + EnumInfos: file_feast_core_IngestionJob_proto_enumTypes, + MessageInfos: file_feast_core_IngestionJob_proto_msgTypes, + }.Build() + File_feast_core_IngestionJob_proto = out.File + file_feast_core_IngestionJob_proto_rawDesc = nil + file_feast_core_IngestionJob_proto_goTypes = nil + file_feast_core_IngestionJob_proto_depIdxs = nil +} diff --git a/sdk/go/protos/feast/core/Runner.pb.go b/sdk/go/protos/feast/core/Runner.pb.go new file mode 100644 index 00000000000..54e03714d2a --- /dev/null +++ b/sdk/go/protos/feast/core/Runner.pb.go @@ -0,0 +1,376 @@ +// +// * Copyright 2020 The Feast Authors +// * +// * 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.23.0 +// protoc v3.10.0 +// source: feast/core/Runner.proto + +package core + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +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 that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type DirectRunnerConfigOptions struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + //* + // Controls the amount of target parallelism the DirectRunner will use. + // Defaults to the greater of the number of available processors and 3. Must be a value + // greater than zero. + TargetParallelism int32 `protobuf:"varint,1,opt,name=targetParallelism,proto3" json:"targetParallelism,omitempty"` + // BigQuery table specification, e.g. PROJECT_ID:DATASET_ID.PROJECT_ID + DeadLetterTableSpec string `protobuf:"bytes,2,opt,name=deadLetterTableSpec,proto3" json:"deadLetterTableSpec,omitempty"` +} + +func (x *DirectRunnerConfigOptions) Reset() { + *x = DirectRunnerConfigOptions{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_Runner_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DirectRunnerConfigOptions) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DirectRunnerConfigOptions) ProtoMessage() {} + +func (x *DirectRunnerConfigOptions) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_Runner_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DirectRunnerConfigOptions.ProtoReflect.Descriptor instead. +func (*DirectRunnerConfigOptions) Descriptor() ([]byte, []int) { + return file_feast_core_Runner_proto_rawDescGZIP(), []int{0} +} + +func (x *DirectRunnerConfigOptions) GetTargetParallelism() int32 { + if x != nil { + return x.TargetParallelism + } + return 0 +} + +func (x *DirectRunnerConfigOptions) GetDeadLetterTableSpec() string { + if x != nil { + return x.DeadLetterTableSpec + } + return "" +} + +type DataflowRunnerConfigOptions struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Project id to use when launching jobs. + Project string `protobuf:"bytes,1,opt,name=project,proto3" json:"project,omitempty"` + // The Google Compute Engine region for creating Dataflow jobs. + Region string `protobuf:"bytes,2,opt,name=region,proto3" json:"region,omitempty"` + // GCP availability zone for operations. + Zone string `protobuf:"bytes,3,opt,name=zone,proto3" json:"zone,omitempty"` + // Run the job as a specific service account, instead of the default GCE robot. + ServiceAccount string `protobuf:"bytes,4,opt,name=serviceAccount,proto3" json:"serviceAccount,omitempty"` + // GCE network for launching workers. + Network string `protobuf:"bytes,5,opt,name=network,proto3" json:"network,omitempty"` + // GCE subnetwork for launching workers. e.g. regions/asia-east1/subnetworks/mysubnetwork + Subnetwork string `protobuf:"bytes,6,opt,name=subnetwork,proto3" json:"subnetwork,omitempty"` + // Machine type to create Dataflow worker VMs as. + WorkerMachineType string `protobuf:"bytes,7,opt,name=workerMachineType,proto3" json:"workerMachineType,omitempty"` + // The autoscaling algorithm to use for the workerpool. + AutoscalingAlgorithm string `protobuf:"bytes,8,opt,name=autoscalingAlgorithm,proto3" json:"autoscalingAlgorithm,omitempty"` + // Specifies whether worker pools should be started with public IP addresses. + UsePublicIps bool `protobuf:"varint,9,opt,name=usePublicIps,proto3" json:"usePublicIps,omitempty"` + // A pipeline level default location for storing temporary files. Support Google Cloud Storage locations, + // e.g. gs://bucket/object + TempLocation string `protobuf:"bytes,10,opt,name=tempLocation,proto3" json:"tempLocation,omitempty"` + // The maximum number of workers to use for the workerpool. + MaxNumWorkers int32 `protobuf:"varint,11,opt,name=maxNumWorkers,proto3" json:"maxNumWorkers,omitempty"` + // BigQuery table specification, e.g. PROJECT_ID:DATASET_ID.PROJECT_ID + DeadLetterTableSpec string `protobuf:"bytes,12,opt,name=deadLetterTableSpec,proto3" json:"deadLetterTableSpec,omitempty"` +} + +func (x *DataflowRunnerConfigOptions) Reset() { + *x = DataflowRunnerConfigOptions{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_Runner_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DataflowRunnerConfigOptions) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DataflowRunnerConfigOptions) ProtoMessage() {} + +func (x *DataflowRunnerConfigOptions) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_Runner_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DataflowRunnerConfigOptions.ProtoReflect.Descriptor instead. +func (*DataflowRunnerConfigOptions) Descriptor() ([]byte, []int) { + return file_feast_core_Runner_proto_rawDescGZIP(), []int{1} +} + +func (x *DataflowRunnerConfigOptions) GetProject() string { + if x != nil { + return x.Project + } + return "" +} + +func (x *DataflowRunnerConfigOptions) GetRegion() string { + if x != nil { + return x.Region + } + return "" +} + +func (x *DataflowRunnerConfigOptions) GetZone() string { + if x != nil { + return x.Zone + } + return "" +} + +func (x *DataflowRunnerConfigOptions) GetServiceAccount() string { + if x != nil { + return x.ServiceAccount + } + return "" +} + +func (x *DataflowRunnerConfigOptions) GetNetwork() string { + if x != nil { + return x.Network + } + return "" +} + +func (x *DataflowRunnerConfigOptions) GetSubnetwork() string { + if x != nil { + return x.Subnetwork + } + return "" +} + +func (x *DataflowRunnerConfigOptions) GetWorkerMachineType() string { + if x != nil { + return x.WorkerMachineType + } + return "" +} + +func (x *DataflowRunnerConfigOptions) GetAutoscalingAlgorithm() string { + if x != nil { + return x.AutoscalingAlgorithm + } + return "" +} + +func (x *DataflowRunnerConfigOptions) GetUsePublicIps() bool { + if x != nil { + return x.UsePublicIps + } + return false +} + +func (x *DataflowRunnerConfigOptions) GetTempLocation() string { + if x != nil { + return x.TempLocation + } + return "" +} + +func (x *DataflowRunnerConfigOptions) GetMaxNumWorkers() int32 { + if x != nil { + return x.MaxNumWorkers + } + return 0 +} + +func (x *DataflowRunnerConfigOptions) GetDeadLetterTableSpec() string { + if x != nil { + return x.DeadLetterTableSpec + } + return "" +} + +var File_feast_core_Runner_proto protoreflect.FileDescriptor + +var file_feast_core_Runner_proto_rawDesc = []byte{ + 0x0a, 0x17, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x52, 0x75, 0x6e, + 0x6e, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x66, 0x65, 0x61, 0x73, 0x74, + 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x22, 0x7b, 0x0a, 0x19, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x52, + 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x50, 0x61, 0x72, 0x61, + 0x6c, 0x6c, 0x65, 0x6c, 0x69, 0x73, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x69, 0x73, 0x6d, + 0x12, 0x30, 0x0a, 0x13, 0x64, 0x65, 0x61, 0x64, 0x4c, 0x65, 0x74, 0x74, 0x65, 0x72, 0x54, 0x61, + 0x62, 0x6c, 0x65, 0x53, 0x70, 0x65, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x64, + 0x65, 0x61, 0x64, 0x4c, 0x65, 0x74, 0x74, 0x65, 0x72, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x70, + 0x65, 0x63, 0x22, 0xc7, 0x03, 0x0a, 0x1b, 0x44, 0x61, 0x74, 0x61, 0x66, 0x6c, 0x6f, 0x77, 0x52, + 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, + 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x7a, 0x6f, 0x6e, 0x65, 0x12, 0x26, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x75, + 0x62, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x2c, 0x0a, 0x11, 0x77, 0x6f, + 0x72, 0x6b, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4d, 0x61, 0x63, + 0x68, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x32, 0x0a, 0x14, 0x61, 0x75, 0x74, 0x6f, + 0x73, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x61, 0x75, 0x74, 0x6f, 0x73, 0x63, 0x61, 0x6c, + 0x69, 0x6e, 0x67, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x12, 0x22, 0x0a, 0x0c, + 0x75, 0x73, 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x70, 0x73, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0c, 0x75, 0x73, 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x70, 0x73, + 0x12, 0x22, 0x0a, 0x0c, 0x74, 0x65, 0x6d, 0x70, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x65, 0x6d, 0x70, 0x4c, 0x6f, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x4e, 0x75, 0x6d, 0x57, 0x6f, + 0x72, 0x6b, 0x65, 0x72, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x6d, 0x61, 0x78, + 0x4e, 0x75, 0x6d, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x12, 0x30, 0x0a, 0x13, 0x64, 0x65, + 0x61, 0x64, 0x4c, 0x65, 0x74, 0x74, 0x65, 0x72, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x70, 0x65, + 0x63, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x64, 0x65, 0x61, 0x64, 0x4c, 0x65, 0x74, + 0x74, 0x65, 0x72, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x70, 0x65, 0x63, 0x42, 0x54, 0x0a, 0x10, + 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x72, 0x65, + 0x42, 0x0b, 0x52, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x5a, 0x33, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2d, + 0x64, 0x65, 0x76, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x67, 0x6f, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x63, 0x6f, + 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_feast_core_Runner_proto_rawDescOnce sync.Once + file_feast_core_Runner_proto_rawDescData = file_feast_core_Runner_proto_rawDesc +) + +func file_feast_core_Runner_proto_rawDescGZIP() []byte { + file_feast_core_Runner_proto_rawDescOnce.Do(func() { + file_feast_core_Runner_proto_rawDescData = protoimpl.X.CompressGZIP(file_feast_core_Runner_proto_rawDescData) + }) + return file_feast_core_Runner_proto_rawDescData +} + +var file_feast_core_Runner_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_feast_core_Runner_proto_goTypes = []interface{}{ + (*DirectRunnerConfigOptions)(nil), // 0: feast.core.DirectRunnerConfigOptions + (*DataflowRunnerConfigOptions)(nil), // 1: feast.core.DataflowRunnerConfigOptions +} +var file_feast_core_Runner_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_feast_core_Runner_proto_init() } +func file_feast_core_Runner_proto_init() { + if File_feast_core_Runner_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_feast_core_Runner_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DirectRunnerConfigOptions); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_Runner_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DataflowRunnerConfigOptions); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_feast_core_Runner_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_feast_core_Runner_proto_goTypes, + DependencyIndexes: file_feast_core_Runner_proto_depIdxs, + MessageInfos: file_feast_core_Runner_proto_msgTypes, + }.Build() + File_feast_core_Runner_proto = out.File + file_feast_core_Runner_proto_rawDesc = nil + file_feast_core_Runner_proto_goTypes = nil + file_feast_core_Runner_proto_depIdxs = nil +} diff --git a/sdk/go/protos/feast/core/Source.pb.go b/sdk/go/protos/feast/core/Source.pb.go index 090210a4961..b29dc5af96c 100644 --- a/sdk/go/protos/feast/core/Source.pb.go +++ b/sdk/go/protos/feast/core/Source.pb.go @@ -1,24 +1,45 @@ +// +// * Copyright 2019 The Feast Authors +// * +// * 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.23.0 +// protoc v3.10.0 // source: feast/core/Source.proto package core import ( - fmt "fmt" proto "github.com/golang/protobuf/proto" - math "math" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" ) -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf +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 proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 type SourceType int32 @@ -27,79 +48,98 @@ const ( SourceType_KAFKA SourceType = 1 ) -var SourceType_name = map[int32]string{ - 0: "INVALID", - 1: "KAFKA", -} +// Enum value maps for SourceType. +var ( + SourceType_name = map[int32]string{ + 0: "INVALID", + 1: "KAFKA", + } + SourceType_value = map[string]int32{ + "INVALID": 0, + "KAFKA": 1, + } +) -var SourceType_value = map[string]int32{ - "INVALID": 0, - "KAFKA": 1, +func (x SourceType) Enum() *SourceType { + p := new(SourceType) + *p = x + return p } func (x SourceType) String() string { - return proto.EnumName(SourceType_name, int32(x)) + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } +func (SourceType) Descriptor() protoreflect.EnumDescriptor { + return file_feast_core_Source_proto_enumTypes[0].Descriptor() +} + +func (SourceType) Type() protoreflect.EnumType { + return &file_feast_core_Source_proto_enumTypes[0] +} + +func (x SourceType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SourceType.Descriptor instead. func (SourceType) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_4d161c4e53091468, []int{0} + return file_feast_core_Source_proto_rawDescGZIP(), []int{0} } type Source struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // The kind of data source Feast should connect to in order to retrieve FeatureRow value Type SourceType `protobuf:"varint,1,opt,name=type,proto3,enum=feast.core.SourceType" json:"type,omitempty"` // Source specific configuration // - // Types that are valid to be assigned to SourceConfig: + // Types that are assignable to SourceConfig: // *Source_KafkaSourceConfig - SourceConfig isSource_SourceConfig `protobuf_oneof:"source_config"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + SourceConfig isSource_SourceConfig `protobuf_oneof:"source_config"` } -func (m *Source) Reset() { *m = Source{} } -func (m *Source) String() string { return proto.CompactTextString(m) } -func (*Source) ProtoMessage() {} -func (*Source) Descriptor() ([]byte, []int) { - return fileDescriptor_4d161c4e53091468, []int{0} +func (x *Source) Reset() { + *x = Source{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_Source_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *Source) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Source.Unmarshal(m, b) -} -func (m *Source) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Source.Marshal(b, m, deterministic) -} -func (m *Source) XXX_Merge(src proto.Message) { - xxx_messageInfo_Source.Merge(m, src) -} -func (m *Source) XXX_Size() int { - return xxx_messageInfo_Source.Size(m) -} -func (m *Source) XXX_DiscardUnknown() { - xxx_messageInfo_Source.DiscardUnknown(m) +func (x *Source) String() string { + return protoimpl.X.MessageStringOf(x) } -var xxx_messageInfo_Source proto.InternalMessageInfo +func (*Source) ProtoMessage() {} -func (m *Source) GetType() SourceType { - if m != nil { - return m.Type +func (x *Source) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_Source_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return SourceType_INVALID + return mi.MessageOf(x) } -type isSource_SourceConfig interface { - isSource_SourceConfig() +// Deprecated: Use Source.ProtoReflect.Descriptor instead. +func (*Source) Descriptor() ([]byte, []int) { + return file_feast_core_Source_proto_rawDescGZIP(), []int{0} } -type Source_KafkaSourceConfig struct { - KafkaSourceConfig *KafkaSourceConfig `protobuf:"bytes,2,opt,name=kafka_source_config,json=kafkaSourceConfig,proto3,oneof"` +func (x *Source) GetType() SourceType { + if x != nil { + return x.Type + } + return SourceType_INVALID } -func (*Source_KafkaSourceConfig) isSource_SourceConfig() {} - func (m *Source) GetSourceConfig() isSource_SourceConfig { if m != nil { return m.SourceConfig @@ -107,95 +147,214 @@ func (m *Source) GetSourceConfig() isSource_SourceConfig { return nil } -func (m *Source) GetKafkaSourceConfig() *KafkaSourceConfig { - if x, ok := m.GetSourceConfig().(*Source_KafkaSourceConfig); ok { +func (x *Source) GetKafkaSourceConfig() *KafkaSourceConfig { + if x, ok := x.GetSourceConfig().(*Source_KafkaSourceConfig); ok { return x.KafkaSourceConfig } return nil } -// XXX_OneofWrappers is for the internal use of the proto package. -func (*Source) XXX_OneofWrappers() []interface{} { - return []interface{}{ - (*Source_KafkaSourceConfig)(nil), - } +type isSource_SourceConfig interface { + isSource_SourceConfig() +} + +type Source_KafkaSourceConfig struct { + KafkaSourceConfig *KafkaSourceConfig `protobuf:"bytes,2,opt,name=kafka_source_config,json=kafkaSourceConfig,proto3,oneof"` } +func (*Source_KafkaSourceConfig) isSource_SourceConfig() {} + type KafkaSourceConfig struct { - // - bootstrapServers: [comma delimited value of host[:port]] + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Comma separated list of Kafka bootstrap servers. Used for feature sets without a defined source host[:port]] BootstrapServers string `protobuf:"bytes,1,opt,name=bootstrap_servers,json=bootstrapServers,proto3" json:"bootstrap_servers,omitempty"` - // - topics: [Kafka topic name. This value is provisioned by core and should not be set by the user.] - Topic string `protobuf:"bytes,2,opt,name=topic,proto3" json:"topic,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + // Kafka topic to use for feature sets without user defined topics + Topic string `protobuf:"bytes,2,opt,name=topic,proto3" json:"topic,omitempty"` + // Number of Kafka partitions to to use for managed feature stream. + Partitions int32 `protobuf:"varint,3,opt,name=partitions,proto3" json:"partitions,omitempty"` + // Defines the number of copies of managed feature stream Kafka. + ReplicationFactor int32 `protobuf:"varint,4,opt,name=replicationFactor,proto3" json:"replicationFactor,omitempty"` } -func (m *KafkaSourceConfig) Reset() { *m = KafkaSourceConfig{} } -func (m *KafkaSourceConfig) String() string { return proto.CompactTextString(m) } -func (*KafkaSourceConfig) ProtoMessage() {} -func (*KafkaSourceConfig) Descriptor() ([]byte, []int) { - return fileDescriptor_4d161c4e53091468, []int{1} +func (x *KafkaSourceConfig) Reset() { + *x = KafkaSourceConfig{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_Source_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *KafkaSourceConfig) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_KafkaSourceConfig.Unmarshal(m, b) -} -func (m *KafkaSourceConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_KafkaSourceConfig.Marshal(b, m, deterministic) -} -func (m *KafkaSourceConfig) XXX_Merge(src proto.Message) { - xxx_messageInfo_KafkaSourceConfig.Merge(m, src) -} -func (m *KafkaSourceConfig) XXX_Size() int { - return xxx_messageInfo_KafkaSourceConfig.Size(m) +func (x *KafkaSourceConfig) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *KafkaSourceConfig) XXX_DiscardUnknown() { - xxx_messageInfo_KafkaSourceConfig.DiscardUnknown(m) + +func (*KafkaSourceConfig) ProtoMessage() {} + +func (x *KafkaSourceConfig) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_Source_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_KafkaSourceConfig proto.InternalMessageInfo +// Deprecated: Use KafkaSourceConfig.ProtoReflect.Descriptor instead. +func (*KafkaSourceConfig) Descriptor() ([]byte, []int) { + return file_feast_core_Source_proto_rawDescGZIP(), []int{1} +} -func (m *KafkaSourceConfig) GetBootstrapServers() string { - if m != nil { - return m.BootstrapServers +func (x *KafkaSourceConfig) GetBootstrapServers() string { + if x != nil { + return x.BootstrapServers } return "" } -func (m *KafkaSourceConfig) GetTopic() string { - if m != nil { - return m.Topic +func (x *KafkaSourceConfig) GetTopic() string { + if x != nil { + return x.Topic } return "" } -func init() { - proto.RegisterEnum("feast.core.SourceType", SourceType_name, SourceType_value) - proto.RegisterType((*Source)(nil), "feast.core.Source") - proto.RegisterType((*KafkaSourceConfig)(nil), "feast.core.KafkaSourceConfig") -} - -func init() { proto.RegisterFile("feast/core/Source.proto", fileDescriptor_4d161c4e53091468) } - -var fileDescriptor_4d161c4e53091468 = []byte{ - // 273 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x90, 0xd1, 0x4a, 0xc3, 0x30, - 0x14, 0x86, 0x57, 0x71, 0x93, 0x9e, 0xa2, 0xb6, 0x51, 0x74, 0x37, 0xc2, 0x18, 0x5e, 0x8c, 0x09, - 0x0d, 0xcc, 0x27, 0x68, 0x15, 0x71, 0x56, 0x54, 0x3a, 0xd9, 0x85, 0x37, 0xa5, 0xad, 0x69, 0xad, - 0x41, 0x4f, 0x48, 0x32, 0x61, 0x2f, 0xe2, 0xf3, 0x4a, 0x13, 0xb0, 0xd3, 0x5d, 0xf6, 0xff, 0xbf, - 0xbf, 0x9c, 0x7c, 0x70, 0x5a, 0xb1, 0x5c, 0x69, 0x5a, 0xa2, 0x64, 0x74, 0x81, 0x2b, 0x59, 0xb2, - 0x50, 0x48, 0xd4, 0x48, 0xc0, 0x14, 0x61, 0x5b, 0x8c, 0xbf, 0x1d, 0x18, 0xd8, 0x92, 0x4c, 0x61, - 0x57, 0xaf, 0x05, 0x1b, 0x3a, 0x23, 0x67, 0x72, 0x30, 0x3b, 0x09, 0x3b, 0x2a, 0xb4, 0xc4, 0xf3, - 0x5a, 0xb0, 0xd4, 0x30, 0xe4, 0x11, 0x8e, 0x78, 0x5e, 0xf1, 0x3c, 0x53, 0xa6, 0xc9, 0x4a, 0xfc, - 0xac, 0x9a, 0x7a, 0xb8, 0x33, 0x72, 0x26, 0xde, 0xec, 0x6c, 0x73, 0x9a, 0xb4, 0x98, 0xdd, 0x5f, - 0x19, 0xe8, 0xb6, 0x97, 0x06, 0xfc, 0x7f, 0x18, 0x1f, 0xc2, 0xfe, 0x9f, 0x5f, 0x8d, 0x97, 0x10, - 0x6c, 0x4d, 0xc9, 0x05, 0x04, 0x05, 0xa2, 0x56, 0x5a, 0xe6, 0x22, 0x53, 0x4c, 0x7e, 0x31, 0xa9, - 0xcc, 0xbd, 0x6e, 0xea, 0xff, 0x16, 0x0b, 0x9b, 0x93, 0x63, 0xe8, 0x6b, 0x14, 0x4d, 0x69, 0xae, - 0x72, 0x53, 0xfb, 0x31, 0x3d, 0x07, 0xe8, 0x5e, 0x43, 0x3c, 0xd8, 0x9b, 0x3f, 0x2c, 0xa3, 0xfb, - 0xf9, 0xb5, 0xdf, 0x23, 0x2e, 0xf4, 0x93, 0xe8, 0x26, 0x89, 0x7c, 0x27, 0xbe, 0x83, 0x0d, 0x49, - 0xb1, 0x67, 0x17, 0x4f, 0xad, 0xbd, 0x17, 0x5a, 0x37, 0xfa, 0x6d, 0x55, 0x84, 0x25, 0x7e, 0xd0, - 0x1a, 0xdf, 0x19, 0xa7, 0xd6, 0xb3, 0x7a, 0xe5, 0xb4, 0x46, 0x6a, 0x14, 0x2b, 0xda, 0xb9, 0x2f, - 0x06, 0x26, 0xba, 0xfc, 0x09, 0x00, 0x00, 0xff, 0xff, 0xc3, 0xc9, 0xf2, 0x62, 0x90, 0x01, 0x00, - 0x00, +func (x *KafkaSourceConfig) GetPartitions() int32 { + if x != nil { + return x.Partitions + } + return 0 +} + +func (x *KafkaSourceConfig) GetReplicationFactor() int32 { + if x != nil { + return x.ReplicationFactor + } + return 0 +} + +var File_feast_core_Source_proto protoreflect.FileDescriptor + +var file_feast_core_Source_proto_rawDesc = []byte{ + 0x0a, 0x17, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x53, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x66, 0x65, 0x61, 0x73, 0x74, + 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x22, 0x96, 0x01, 0x0a, 0x06, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x12, 0x2a, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, + 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x4f, 0x0a, 0x13, + 0x6b, 0x61, 0x66, 0x6b, 0x61, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x66, 0x65, 0x61, 0x73, + 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4b, 0x61, 0x66, 0x6b, 0x61, 0x53, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x48, 0x00, 0x52, 0x11, 0x6b, 0x61, 0x66, 0x6b, + 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x0f, 0x0a, + 0x0d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xa4, + 0x01, 0x0a, 0x11, 0x4b, 0x61, 0x66, 0x6b, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2b, 0x0a, 0x11, 0x62, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, + 0x70, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x10, 0x62, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x74, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x70, 0x61, 0x72, + 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x72, 0x65, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x11, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, + 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2a, 0x24, 0x0a, 0x0a, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x00, + 0x12, 0x09, 0x0a, 0x05, 0x4b, 0x41, 0x46, 0x4b, 0x41, 0x10, 0x01, 0x42, 0x54, 0x0a, 0x10, 0x66, + 0x65, 0x61, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x42, + 0x0b, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x5a, 0x33, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2d, 0x64, + 0x65, 0x76, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x67, 0x6f, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x63, 0x6f, 0x72, + 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_feast_core_Source_proto_rawDescOnce sync.Once + file_feast_core_Source_proto_rawDescData = file_feast_core_Source_proto_rawDesc +) + +func file_feast_core_Source_proto_rawDescGZIP() []byte { + file_feast_core_Source_proto_rawDescOnce.Do(func() { + file_feast_core_Source_proto_rawDescData = protoimpl.X.CompressGZIP(file_feast_core_Source_proto_rawDescData) + }) + return file_feast_core_Source_proto_rawDescData +} + +var file_feast_core_Source_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_feast_core_Source_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_feast_core_Source_proto_goTypes = []interface{}{ + (SourceType)(0), // 0: feast.core.SourceType + (*Source)(nil), // 1: feast.core.Source + (*KafkaSourceConfig)(nil), // 2: feast.core.KafkaSourceConfig +} +var file_feast_core_Source_proto_depIdxs = []int32{ + 0, // 0: feast.core.Source.type:type_name -> feast.core.SourceType + 2, // 1: feast.core.Source.kafka_source_config:type_name -> feast.core.KafkaSourceConfig + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_feast_core_Source_proto_init() } +func file_feast_core_Source_proto_init() { + if File_feast_core_Source_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_feast_core_Source_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Source); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_Source_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*KafkaSourceConfig); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_feast_core_Source_proto_msgTypes[0].OneofWrappers = []interface{}{ + (*Source_KafkaSourceConfig)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_feast_core_Source_proto_rawDesc, + NumEnums: 1, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_feast_core_Source_proto_goTypes, + DependencyIndexes: file_feast_core_Source_proto_depIdxs, + EnumInfos: file_feast_core_Source_proto_enumTypes, + MessageInfos: file_feast_core_Source_proto_msgTypes, + }.Build() + File_feast_core_Source_proto = out.File + file_feast_core_Source_proto_rawDesc = nil + file_feast_core_Source_proto_goTypes = nil + file_feast_core_Source_proto_depIdxs = nil } diff --git a/sdk/go/protos/feast/core/Store.pb.go b/sdk/go/protos/feast/core/Store.pb.go index 9120edcb42c..866dc71fe94 100644 --- a/sdk/go/protos/feast/core/Store.pb.go +++ b/sdk/go/protos/feast/core/Store.pb.go @@ -1,24 +1,45 @@ +// +// * Copyright 2019 The Feast Authors +// * +// * 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.23.0 +// protoc v3.10.0 // source: feast/core/Store.proto package core import ( - fmt "fmt" proto "github.com/golang/protobuf/proto" - math "math" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" ) -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf +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 proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 type Store_StoreType int32 @@ -37,12 +58,7 @@ const ( Store_REDIS Store_StoreType = 1 // BigQuery stores a FeatureRow element as a row in a BigQuery table. // - // Table name is derived from the feature set name and version as: - // [feature_set_name]_v[feature_set_version] - // - // For example: - // A feature row for feature set "driver" and version "1" will be written - // to table "driver_v1". + // Table name is derived is the same as the feature set name. // // The entities and features in a FeatureSetSpec corresponds to the // fields in the BigQuery table (these make up the BigQuery schema). @@ -58,16 +74,12 @@ const ( // ====================|==================|================================ // - event_timestamp | TIMESTAMP | event time of the FeatureRow // - created_timestamp | TIMESTAMP | processing time of the ingestion of the FeatureRow + // - ingestion_id | STRING | unique id identifying groups of rows that have been ingested together // - job_id | STRING | identifier for the job that writes the FeatureRow to the corresponding BigQuery table // // BigQuery table created will be partitioned by the field "event_timestamp" // of the FeatureRow (https://cloud.google.com/bigquery/docs/partitioned-tables). // - // Since newer version of feature set can introduce breaking, non backward- - // compatible BigQuery schema updates, incrementing the version of a - // feature set will result in the creation of a new empty BigQuery table - // with the new schema. - // // The following table shows how ValueType in Feast is mapped to // BigQuery Standard SQL data types // (https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types): @@ -92,29 +104,53 @@ const ( // Store_BIGQUERY Store_StoreType = 2 // Unsupported in Feast 0.3 - Store_CASSANDRA Store_StoreType = 3 + Store_CASSANDRA Store_StoreType = 3 + Store_REDIS_CLUSTER Store_StoreType = 4 ) -var Store_StoreType_name = map[int32]string{ - 0: "INVALID", - 1: "REDIS", - 2: "BIGQUERY", - 3: "CASSANDRA", -} +// Enum value maps for Store_StoreType. +var ( + Store_StoreType_name = map[int32]string{ + 0: "INVALID", + 1: "REDIS", + 2: "BIGQUERY", + 3: "CASSANDRA", + 4: "REDIS_CLUSTER", + } + Store_StoreType_value = map[string]int32{ + "INVALID": 0, + "REDIS": 1, + "BIGQUERY": 2, + "CASSANDRA": 3, + "REDIS_CLUSTER": 4, + } +) -var Store_StoreType_value = map[string]int32{ - "INVALID": 0, - "REDIS": 1, - "BIGQUERY": 2, - "CASSANDRA": 3, +func (x Store_StoreType) Enum() *Store_StoreType { + p := new(Store_StoreType) + *p = x + return p } func (x Store_StoreType) String() string { - return proto.EnumName(Store_StoreType_name, int32(x)) + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Store_StoreType) Descriptor() protoreflect.EnumDescriptor { + return file_feast_core_Store_proto_enumTypes[0].Descriptor() +} + +func (Store_StoreType) Type() protoreflect.EnumType { + return &file_feast_core_Store_proto_enumTypes[0] } +func (x Store_StoreType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Store_StoreType.Descriptor instead. func (Store_StoreType) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_4b177bc9ccf64875, []int{0, 0} + return file_feast_core_Store_proto_rawDescGZIP(), []int{0, 0} } // Store provides a location where Feast reads and writes feature values. @@ -126,6 +162,10 @@ func (Store_StoreType) EnumDescriptor() ([]byte, []int) { // BigQuery but it will materialize as a key, value pair element in Redis. // type Store struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Name of the store. Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // Type of store. @@ -134,58 +174,98 @@ type Store struct { Subscriptions []*Store_Subscription `protobuf:"bytes,4,rep,name=subscriptions,proto3" json:"subscriptions,omitempty"` // Configuration to connect to the store. Required. // - // Types that are valid to be assigned to Config: + // Types that are assignable to Config: // *Store_RedisConfig_ // *Store_BigqueryConfig // *Store_CassandraConfig_ - Config isStore_Config `protobuf_oneof:"config"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + // *Store_RedisClusterConfig_ + Config isStore_Config `protobuf_oneof:"config"` } -func (m *Store) Reset() { *m = Store{} } -func (m *Store) String() string { return proto.CompactTextString(m) } -func (*Store) ProtoMessage() {} -func (*Store) Descriptor() ([]byte, []int) { - return fileDescriptor_4b177bc9ccf64875, []int{0} +func (x *Store) Reset() { + *x = Store{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_Store_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *Store) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Store.Unmarshal(m, b) +func (x *Store) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *Store) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Store.Marshal(b, m, deterministic) + +func (*Store) ProtoMessage() {} + +func (x *Store) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_Store_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -func (m *Store) XXX_Merge(src proto.Message) { - xxx_messageInfo_Store.Merge(m, src) + +// Deprecated: Use Store.ProtoReflect.Descriptor instead. +func (*Store) Descriptor() ([]byte, []int) { + return file_feast_core_Store_proto_rawDescGZIP(), []int{0} } -func (m *Store) XXX_Size() int { - return xxx_messageInfo_Store.Size(m) + +func (x *Store) GetName() string { + if x != nil { + return x.Name + } + return "" } -func (m *Store) XXX_DiscardUnknown() { - xxx_messageInfo_Store.DiscardUnknown(m) + +func (x *Store) GetType() Store_StoreType { + if x != nil { + return x.Type + } + return Store_INVALID } -var xxx_messageInfo_Store proto.InternalMessageInfo +func (x *Store) GetSubscriptions() []*Store_Subscription { + if x != nil { + return x.Subscriptions + } + return nil +} -func (m *Store) GetName() string { +func (m *Store) GetConfig() isStore_Config { if m != nil { - return m.Name + return m.Config } - return "" + return nil } -func (m *Store) GetType() Store_StoreType { - if m != nil { - return m.Type +func (x *Store) GetRedisConfig() *Store_RedisConfig { + if x, ok := x.GetConfig().(*Store_RedisConfig_); ok { + return x.RedisConfig } - return Store_INVALID + return nil } -func (m *Store) GetSubscriptions() []*Store_Subscription { - if m != nil { - return m.Subscriptions +func (x *Store) GetBigqueryConfig() *Store_BigQueryConfig { + if x, ok := x.GetConfig().(*Store_BigqueryConfig); ok { + return x.BigqueryConfig + } + return nil +} + +func (x *Store) GetCassandraConfig() *Store_CassandraConfig { + if x, ok := x.GetConfig().(*Store_CassandraConfig_); ok { + return x.CassandraConfig + } + return nil +} + +func (x *Store) GetRedisClusterConfig() *Store_RedisClusterConfig { + if x, ok := x.GetConfig().(*Store_RedisClusterConfig_); ok { + return x.RedisClusterConfig } return nil } @@ -206,191 +286,295 @@ type Store_CassandraConfig_ struct { CassandraConfig *Store_CassandraConfig `protobuf:"bytes,13,opt,name=cassandra_config,json=cassandraConfig,proto3,oneof"` } +type Store_RedisClusterConfig_ struct { + RedisClusterConfig *Store_RedisClusterConfig `protobuf:"bytes,14,opt,name=redis_cluster_config,json=redisClusterConfig,proto3,oneof"` +} + func (*Store_RedisConfig_) isStore_Config() {} func (*Store_BigqueryConfig) isStore_Config() {} func (*Store_CassandraConfig_) isStore_Config() {} -func (m *Store) GetConfig() isStore_Config { - if m != nil { - return m.Config +func (*Store_RedisClusterConfig_) isStore_Config() {} + +type Store_RedisConfig struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` + Port int32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` + // Optional. The number of milliseconds to wait before retrying failed Redis connection. + // By default, Feast uses exponential backoff policy and "initial_backoff_ms" sets the initial wait duration. + InitialBackoffMs int32 `protobuf:"varint,3,opt,name=initial_backoff_ms,json=initialBackoffMs,proto3" json:"initial_backoff_ms,omitempty"` + // Optional. Maximum total number of retries for connecting to Redis. Default to zero retries. + MaxRetries int32 `protobuf:"varint,4,opt,name=max_retries,json=maxRetries,proto3" json:"max_retries,omitempty"` +} + +func (x *Store_RedisConfig) Reset() { + *x = Store_RedisConfig{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_Store_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } - return nil } -func (m *Store) GetRedisConfig() *Store_RedisConfig { - if x, ok := m.GetConfig().(*Store_RedisConfig_); ok { - return x.RedisConfig - } - return nil +func (x *Store_RedisConfig) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *Store) GetBigqueryConfig() *Store_BigQueryConfig { - if x, ok := m.GetConfig().(*Store_BigqueryConfig); ok { - return x.BigqueryConfig +func (*Store_RedisConfig) ProtoMessage() {} + +func (x *Store_RedisConfig) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_Store_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return nil + return mi.MessageOf(x) } -func (m *Store) GetCassandraConfig() *Store_CassandraConfig { - if x, ok := m.GetConfig().(*Store_CassandraConfig_); ok { - return x.CassandraConfig +// Deprecated: Use Store_RedisConfig.ProtoReflect.Descriptor instead. +func (*Store_RedisConfig) Descriptor() ([]byte, []int) { + return file_feast_core_Store_proto_rawDescGZIP(), []int{0, 0} +} + +func (x *Store_RedisConfig) GetHost() string { + if x != nil { + return x.Host } - return nil + return "" } -// XXX_OneofWrappers is for the internal use of the proto package. -func (*Store) XXX_OneofWrappers() []interface{} { - return []interface{}{ - (*Store_RedisConfig_)(nil), - (*Store_BigqueryConfig)(nil), - (*Store_CassandraConfig_)(nil), +func (x *Store_RedisConfig) GetPort() int32 { + if x != nil { + return x.Port } + return 0 } -type Store_RedisConfig struct { - Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` - Port int32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` +func (x *Store_RedisConfig) GetInitialBackoffMs() int32 { + if x != nil { + return x.InitialBackoffMs + } + return 0 } -func (m *Store_RedisConfig) Reset() { *m = Store_RedisConfig{} } -func (m *Store_RedisConfig) String() string { return proto.CompactTextString(m) } -func (*Store_RedisConfig) ProtoMessage() {} -func (*Store_RedisConfig) Descriptor() ([]byte, []int) { - return fileDescriptor_4b177bc9ccf64875, []int{0, 0} +func (x *Store_RedisConfig) GetMaxRetries() int32 { + if x != nil { + return x.MaxRetries + } + return 0 } -func (m *Store_RedisConfig) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Store_RedisConfig.Unmarshal(m, b) +type Store_BigQueryConfig struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ProjectId string `protobuf:"bytes,1,opt,name=project_id,json=projectId,proto3" json:"project_id,omitempty"` + DatasetId string `protobuf:"bytes,2,opt,name=dataset_id,json=datasetId,proto3" json:"dataset_id,omitempty"` + StagingLocation string `protobuf:"bytes,3,opt,name=staging_location,json=stagingLocation,proto3" json:"staging_location,omitempty"` + InitialRetryDelaySeconds int32 `protobuf:"varint,4,opt,name=initial_retry_delay_seconds,json=initialRetryDelaySeconds,proto3" json:"initial_retry_delay_seconds,omitempty"` + TotalTimeoutSeconds int32 `protobuf:"varint,5,opt,name=total_timeout_seconds,json=totalTimeoutSeconds,proto3" json:"total_timeout_seconds,omitempty"` +} + +func (x *Store_BigQueryConfig) Reset() { + *x = Store_BigQueryConfig{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_Store_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *Store_RedisConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Store_RedisConfig.Marshal(b, m, deterministic) + +func (x *Store_BigQueryConfig) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *Store_RedisConfig) XXX_Merge(src proto.Message) { - xxx_messageInfo_Store_RedisConfig.Merge(m, src) + +func (*Store_BigQueryConfig) ProtoMessage() {} + +func (x *Store_BigQueryConfig) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_Store_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -func (m *Store_RedisConfig) XXX_Size() int { - return xxx_messageInfo_Store_RedisConfig.Size(m) + +// Deprecated: Use Store_BigQueryConfig.ProtoReflect.Descriptor instead. +func (*Store_BigQueryConfig) Descriptor() ([]byte, []int) { + return file_feast_core_Store_proto_rawDescGZIP(), []int{0, 1} } -func (m *Store_RedisConfig) XXX_DiscardUnknown() { - xxx_messageInfo_Store_RedisConfig.DiscardUnknown(m) + +func (x *Store_BigQueryConfig) GetProjectId() string { + if x != nil { + return x.ProjectId + } + return "" } -var xxx_messageInfo_Store_RedisConfig proto.InternalMessageInfo +func (x *Store_BigQueryConfig) GetDatasetId() string { + if x != nil { + return x.DatasetId + } + return "" +} -func (m *Store_RedisConfig) GetHost() string { - if m != nil { - return m.Host +func (x *Store_BigQueryConfig) GetStagingLocation() string { + if x != nil { + return x.StagingLocation } return "" } -func (m *Store_RedisConfig) GetPort() int32 { - if m != nil { - return m.Port +func (x *Store_BigQueryConfig) GetInitialRetryDelaySeconds() int32 { + if x != nil { + return x.InitialRetryDelaySeconds } return 0 } -type Store_BigQueryConfig struct { - ProjectId string `protobuf:"bytes,1,opt,name=project_id,json=projectId,proto3" json:"project_id,omitempty"` - DatasetId string `protobuf:"bytes,2,opt,name=dataset_id,json=datasetId,proto3" json:"dataset_id,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` +func (x *Store_BigQueryConfig) GetTotalTimeoutSeconds() int32 { + if x != nil { + return x.TotalTimeoutSeconds + } + return 0 } -func (m *Store_BigQueryConfig) Reset() { *m = Store_BigQueryConfig{} } -func (m *Store_BigQueryConfig) String() string { return proto.CompactTextString(m) } -func (*Store_BigQueryConfig) ProtoMessage() {} -func (*Store_BigQueryConfig) Descriptor() ([]byte, []int) { - return fileDescriptor_4b177bc9ccf64875, []int{0, 1} -} +type Store_CassandraConfig struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields -func (m *Store_BigQueryConfig) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Store_BigQueryConfig.Unmarshal(m, b) -} -func (m *Store_BigQueryConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Store_BigQueryConfig.Marshal(b, m, deterministic) -} -func (m *Store_BigQueryConfig) XXX_Merge(src proto.Message) { - xxx_messageInfo_Store_BigQueryConfig.Merge(m, src) + Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` + Port int32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` } -func (m *Store_BigQueryConfig) XXX_Size() int { - return xxx_messageInfo_Store_BigQueryConfig.Size(m) + +func (x *Store_CassandraConfig) Reset() { + *x = Store_CassandraConfig{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_Store_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *Store_BigQueryConfig) XXX_DiscardUnknown() { - xxx_messageInfo_Store_BigQueryConfig.DiscardUnknown(m) + +func (x *Store_CassandraConfig) String() string { + return protoimpl.X.MessageStringOf(x) } -var xxx_messageInfo_Store_BigQueryConfig proto.InternalMessageInfo +func (*Store_CassandraConfig) ProtoMessage() {} -func (m *Store_BigQueryConfig) GetProjectId() string { - if m != nil { - return m.ProjectId +func (x *Store_CassandraConfig) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_Store_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return "" + return mi.MessageOf(x) } -func (m *Store_BigQueryConfig) GetDatasetId() string { - if m != nil { - return m.DatasetId +// Deprecated: Use Store_CassandraConfig.ProtoReflect.Descriptor instead. +func (*Store_CassandraConfig) Descriptor() ([]byte, []int) { + return file_feast_core_Store_proto_rawDescGZIP(), []int{0, 2} +} + +func (x *Store_CassandraConfig) GetHost() string { + if x != nil { + return x.Host } return "" } -type Store_CassandraConfig struct { - Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` - Port int32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` +func (x *Store_CassandraConfig) GetPort() int32 { + if x != nil { + return x.Port + } + return 0 } -func (m *Store_CassandraConfig) Reset() { *m = Store_CassandraConfig{} } -func (m *Store_CassandraConfig) String() string { return proto.CompactTextString(m) } -func (*Store_CassandraConfig) ProtoMessage() {} -func (*Store_CassandraConfig) Descriptor() ([]byte, []int) { - return fileDescriptor_4b177bc9ccf64875, []int{0, 2} -} +type Store_RedisClusterConfig struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields -func (m *Store_CassandraConfig) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Store_CassandraConfig.Unmarshal(m, b) + // List of Redis Uri for all the nodes in Redis Cluster, comma separated. Eg. host1:6379, host2:6379 + ConnectionString string `protobuf:"bytes,1,opt,name=connection_string,json=connectionString,proto3" json:"connection_string,omitempty"` + InitialBackoffMs int32 `protobuf:"varint,2,opt,name=initial_backoff_ms,json=initialBackoffMs,proto3" json:"initial_backoff_ms,omitempty"` + MaxRetries int32 `protobuf:"varint,3,opt,name=max_retries,json=maxRetries,proto3" json:"max_retries,omitempty"` } -func (m *Store_CassandraConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Store_CassandraConfig.Marshal(b, m, deterministic) -} -func (m *Store_CassandraConfig) XXX_Merge(src proto.Message) { - xxx_messageInfo_Store_CassandraConfig.Merge(m, src) + +func (x *Store_RedisClusterConfig) Reset() { + *x = Store_RedisClusterConfig{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_Store_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *Store_CassandraConfig) XXX_Size() int { - return xxx_messageInfo_Store_CassandraConfig.Size(m) + +func (x *Store_RedisClusterConfig) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *Store_CassandraConfig) XXX_DiscardUnknown() { - xxx_messageInfo_Store_CassandraConfig.DiscardUnknown(m) + +func (*Store_RedisClusterConfig) ProtoMessage() {} + +func (x *Store_RedisClusterConfig) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_Store_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_Store_CassandraConfig proto.InternalMessageInfo +// Deprecated: Use Store_RedisClusterConfig.ProtoReflect.Descriptor instead. +func (*Store_RedisClusterConfig) Descriptor() ([]byte, []int) { + return file_feast_core_Store_proto_rawDescGZIP(), []int{0, 3} +} -func (m *Store_CassandraConfig) GetHost() string { - if m != nil { - return m.Host +func (x *Store_RedisClusterConfig) GetConnectionString() string { + if x != nil { + return x.ConnectionString } return "" } -func (m *Store_CassandraConfig) GetPort() int32 { - if m != nil { - return m.Port +func (x *Store_RedisClusterConfig) GetInitialBackoffMs() int32 { + if x != nil { + return x.InitialBackoffMs + } + return 0 +} + +func (x *Store_RedisClusterConfig) GetMaxRetries() int32 { + if x != nil { + return x.MaxRetries } return 0 } type Store_Subscription struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Name of project that the feature sets belongs to. This can be one of // - [project_name] // - * @@ -406,104 +590,282 @@ type Store_Subscription struct { // - my-feature-set* can be used to match all features prefixed by "my-feature-set" // - my-feature-set-6 can be used to select a single feature set Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - // Versions of the given feature sets that will be returned. - // Valid options for version: - // "latest": only the latest version is returned. - // "*": Subscribe to all versions - // [version number]: pin to a specific version. Project and feature set name must be - // explicitly defined if a specific version is pinned. - Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Store_Subscription) Reset() { *m = Store_Subscription{} } -func (m *Store_Subscription) String() string { return proto.CompactTextString(m) } -func (*Store_Subscription) ProtoMessage() {} -func (*Store_Subscription) Descriptor() ([]byte, []int) { - return fileDescriptor_4b177bc9ccf64875, []int{0, 3} } -func (m *Store_Subscription) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Store_Subscription.Unmarshal(m, b) -} -func (m *Store_Subscription) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Store_Subscription.Marshal(b, m, deterministic) -} -func (m *Store_Subscription) XXX_Merge(src proto.Message) { - xxx_messageInfo_Store_Subscription.Merge(m, src) -} -func (m *Store_Subscription) XXX_Size() int { - return xxx_messageInfo_Store_Subscription.Size(m) +func (x *Store_Subscription) Reset() { + *x = Store_Subscription{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_Store_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *Store_Subscription) XXX_DiscardUnknown() { - xxx_messageInfo_Store_Subscription.DiscardUnknown(m) + +func (x *Store_Subscription) String() string { + return protoimpl.X.MessageStringOf(x) } -var xxx_messageInfo_Store_Subscription proto.InternalMessageInfo +func (*Store_Subscription) ProtoMessage() {} -func (m *Store_Subscription) GetProject() string { - if m != nil { - return m.Project +func (x *Store_Subscription) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_Store_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return "" + return mi.MessageOf(x) } -func (m *Store_Subscription) GetName() string { - if m != nil { - return m.Name +// Deprecated: Use Store_Subscription.ProtoReflect.Descriptor instead. +func (*Store_Subscription) Descriptor() ([]byte, []int) { + return file_feast_core_Store_proto_rawDescGZIP(), []int{0, 4} +} + +func (x *Store_Subscription) GetProject() string { + if x != nil { + return x.Project } return "" } -func (m *Store_Subscription) GetVersion() string { - if m != nil { - return m.Version +func (x *Store_Subscription) GetName() string { + if x != nil { + return x.Name } return "" } -func init() { - proto.RegisterEnum("feast.core.Store_StoreType", Store_StoreType_name, Store_StoreType_value) - proto.RegisterType((*Store)(nil), "feast.core.Store") - proto.RegisterType((*Store_RedisConfig)(nil), "feast.core.Store.RedisConfig") - proto.RegisterType((*Store_BigQueryConfig)(nil), "feast.core.Store.BigQueryConfig") - proto.RegisterType((*Store_CassandraConfig)(nil), "feast.core.Store.CassandraConfig") - proto.RegisterType((*Store_Subscription)(nil), "feast.core.Store.Subscription") -} - -func init() { proto.RegisterFile("feast/core/Store.proto", fileDescriptor_4b177bc9ccf64875) } - -var fileDescriptor_4b177bc9ccf64875 = []byte{ - // 450 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x93, 0x4f, 0x6f, 0xd3, 0x30, - 0x18, 0xc6, 0x97, 0xfe, 0x59, 0x97, 0x37, 0xfd, 0x13, 0xf9, 0x80, 0xa2, 0xa2, 0xa1, 0xb0, 0x53, - 0x4f, 0xb1, 0x54, 0xc4, 0x81, 0x1b, 0x4d, 0x3b, 0x41, 0x04, 0xaa, 0x98, 0x0b, 0x93, 0xe0, 0x32, - 0xa5, 0x89, 0x97, 0x79, 0xd3, 0xe2, 0x60, 0xbb, 0x48, 0xfd, 0xa8, 0x7c, 0x1b, 0x64, 0x27, 0x69, - 0x53, 0xda, 0xc3, 0x2e, 0x91, 0xfd, 0xbc, 0xcf, 0xf3, 0xcb, 0x2b, 0xdb, 0x2f, 0xbc, 0xba, 0xa7, - 0xb1, 0x54, 0x38, 0xe1, 0x82, 0xe2, 0x95, 0xe2, 0x82, 0x06, 0x85, 0xe0, 0x8a, 0x23, 0x30, 0x7a, - 0xa0, 0xf5, 0xab, 0xbf, 0x5d, 0xe8, 0x9a, 0x1a, 0x42, 0xd0, 0xc9, 0xe3, 0x67, 0xea, 0x59, 0xbe, - 0x35, 0xb1, 0x89, 0x59, 0x23, 0x0c, 0x1d, 0xb5, 0x2d, 0xa8, 0xd7, 0xf2, 0xad, 0xc9, 0x70, 0xfa, - 0x3a, 0xd8, 0x07, 0x83, 0x12, 0x68, 0xbe, 0xdf, 0xb7, 0x05, 0x25, 0xc6, 0x88, 0x16, 0x30, 0x90, - 0x9b, 0xb5, 0x4c, 0x04, 0x2b, 0x14, 0xe3, 0xb9, 0xf4, 0x3a, 0x7e, 0x7b, 0xe2, 0x4c, 0xdf, 0x9c, - 0x48, 0x36, 0x6c, 0xe4, 0x30, 0x84, 0x42, 0xe8, 0x0b, 0x9a, 0x32, 0x79, 0x97, 0xf0, 0xfc, 0x9e, - 0x65, 0x9e, 0xe3, 0x5b, 0x13, 0x67, 0x7a, 0x79, 0x0c, 0x21, 0xda, 0x35, 0x37, 0xa6, 0xcf, 0x67, - 0xc4, 0x11, 0xfb, 0x2d, 0xfa, 0x02, 0xa3, 0x35, 0xcb, 0x7e, 0x6f, 0xa8, 0xd8, 0xd6, 0x98, 0xbe, - 0xc1, 0xf8, 0xc7, 0x98, 0x90, 0x65, 0x37, 0xda, 0xb8, 0x23, 0x0d, 0xeb, 0x68, 0x05, 0x5b, 0x82, - 0x9b, 0xc4, 0x52, 0xc6, 0x79, 0x2a, 0xe2, 0x9a, 0x36, 0x30, 0xb4, 0xb7, 0xc7, 0xb4, 0x79, 0xed, - 0xdc, 0xe1, 0x46, 0xc9, 0xa1, 0x34, 0x7e, 0x0f, 0x4e, 0xa3, 0x75, 0x7d, 0xf4, 0x0f, 0x5c, 0xaa, - 0xfa, 0xe8, 0xf5, 0x5a, 0x6b, 0x05, 0x17, 0xca, 0x1c, 0x7d, 0x97, 0x98, 0xf5, 0x78, 0x09, 0xc3, - 0xc3, 0x56, 0xd1, 0x25, 0x40, 0x21, 0xf8, 0x23, 0x4d, 0xd4, 0x1d, 0x4b, 0xab, 0xbc, 0x5d, 0x29, - 0x51, 0xaa, 0xcb, 0x69, 0xac, 0x62, 0x49, 0x4d, 0xb9, 0x55, 0x96, 0x2b, 0x25, 0x4a, 0xc7, 0x1f, - 0x60, 0xf4, 0x5f, 0xb3, 0x2f, 0x6e, 0xe5, 0x16, 0xfa, 0xcd, 0x1b, 0x44, 0x1e, 0xf4, 0xaa, 0xdf, - 0x7a, 0x6d, 0x13, 0xad, 0xb7, 0x27, 0xdf, 0x95, 0x07, 0xbd, 0x3f, 0x54, 0x48, 0xc6, 0xf3, 0xaa, - 0xa9, 0x7a, 0x7b, 0xf5, 0x11, 0xec, 0xdd, 0x9b, 0x42, 0x0e, 0xf4, 0xa2, 0xe5, 0xed, 0xec, 0x6b, - 0xb4, 0x70, 0xcf, 0x90, 0x0d, 0x5d, 0x72, 0xbd, 0x88, 0x56, 0xae, 0x85, 0xfa, 0x70, 0x11, 0x46, - 0x9f, 0x6e, 0x7e, 0x5c, 0x93, 0x9f, 0x6e, 0x0b, 0x0d, 0xc0, 0x9e, 0xcf, 0x56, 0xab, 0xd9, 0x72, - 0x41, 0x66, 0x6e, 0x3b, 0xbc, 0x80, 0xf3, 0xf2, 0x86, 0xc2, 0x08, 0x1a, 0x2f, 0x3d, 0x04, 0xc3, - 0xfd, 0xa6, 0x27, 0xe0, 0x17, 0xce, 0x98, 0x7a, 0xd8, 0xac, 0x83, 0x84, 0x3f, 0xe3, 0x8c, 0x3f, - 0xd2, 0x27, 0x5c, 0x8e, 0x8a, 0x4c, 0x9f, 0x70, 0xc6, 0xb1, 0x19, 0x13, 0x89, 0xf7, 0xe3, 0xb3, - 0x3e, 0x37, 0xd2, 0xbb, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x56, 0xfe, 0x58, 0x14, 0x53, 0x03, - 0x00, 0x00, +var File_feast_core_Store_proto protoreflect.FileDescriptor + +var file_feast_core_Store_proto_rawDesc = []byte{ + 0x0a, 0x16, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x53, 0x74, 0x6f, + 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, + 0x63, 0x6f, 0x72, 0x65, 0x22, 0xb4, 0x09, 0x0a, 0x05, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x1b, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x74, + 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x12, 0x44, 0x0a, 0x0d, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x66, 0x65, 0x61, + 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x75, + 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x73, 0x75, 0x62, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x42, 0x0a, 0x0c, 0x72, 0x65, 0x64, + 0x69, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1d, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x74, 0x6f, + 0x72, 0x65, 0x2e, 0x52, 0x65, 0x64, 0x69, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x48, 0x00, + 0x52, 0x0b, 0x72, 0x65, 0x64, 0x69, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4b, 0x0a, + 0x0f, 0x62, 0x69, 0x67, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x42, 0x69, 0x67, 0x51, 0x75, 0x65, + 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x48, 0x00, 0x52, 0x0e, 0x62, 0x69, 0x67, 0x71, + 0x75, 0x65, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4e, 0x0a, 0x10, 0x63, 0x61, + 0x73, 0x73, 0x61, 0x6e, 0x64, 0x72, 0x61, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x0d, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, + 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x61, 0x73, 0x73, 0x61, 0x6e, 0x64, 0x72, + 0x61, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x48, 0x00, 0x52, 0x0f, 0x63, 0x61, 0x73, 0x73, 0x61, + 0x6e, 0x64, 0x72, 0x61, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x58, 0x0a, 0x14, 0x72, 0x65, + 0x64, 0x69, 0x73, 0x5f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, + 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x64, 0x69, + 0x73, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x48, 0x00, + 0x52, 0x12, 0x72, 0x65, 0x64, 0x69, 0x73, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x1a, 0x84, 0x01, 0x0a, 0x0b, 0x52, 0x65, 0x64, 0x69, 0x73, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x2c, 0x0a, 0x12, + 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x5f, + 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, + 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x4d, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, + 0x78, 0x5f, 0x72, 0x65, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x0a, 0x6d, 0x61, 0x78, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x73, 0x1a, 0xec, 0x01, 0x0a, 0x0e, + 0x42, 0x69, 0x67, 0x51, 0x75, 0x65, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1d, + 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, + 0x0a, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x10, + 0x73, 0x74, 0x61, 0x67, 0x69, 0x6e, 0x67, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x74, 0x61, 0x67, 0x69, 0x6e, 0x67, 0x4c, + 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3d, 0x0a, 0x1b, 0x69, 0x6e, 0x69, 0x74, 0x69, + 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x74, 0x72, 0x79, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x73, + 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x69, 0x6e, + 0x69, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x74, 0x72, 0x79, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x53, + 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, + 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, + 0x6f, 0x75, 0x74, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x1a, 0x39, 0x0a, 0x0f, 0x43, 0x61, + 0x73, 0x73, 0x61, 0x6e, 0x64, 0x72, 0x61, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, + 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, + 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x04, 0x70, 0x6f, 0x72, 0x74, 0x1a, 0x90, 0x01, 0x0a, 0x12, 0x52, 0x65, 0x64, 0x69, 0x73, 0x43, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2b, 0x0a, 0x11, + 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x2c, 0x0a, 0x12, 0x69, 0x6e, 0x69, + 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x5f, 0x6d, 0x73, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x42, 0x61, + 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x4d, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x78, 0x5f, 0x72, + 0x65, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x6d, 0x61, + 0x78, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x73, 0x1a, 0x42, 0x0a, 0x0c, 0x53, 0x75, 0x62, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x6a, + 0x65, 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, + 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0x53, 0x0a, 0x09, + 0x53, 0x74, 0x6f, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x56, + 0x41, 0x4c, 0x49, 0x44, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x45, 0x44, 0x49, 0x53, 0x10, + 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x42, 0x49, 0x47, 0x51, 0x55, 0x45, 0x52, 0x59, 0x10, 0x02, 0x12, + 0x0d, 0x0a, 0x09, 0x43, 0x41, 0x53, 0x53, 0x41, 0x4e, 0x44, 0x52, 0x41, 0x10, 0x03, 0x12, 0x11, + 0x0a, 0x0d, 0x52, 0x45, 0x44, 0x49, 0x53, 0x5f, 0x43, 0x4c, 0x55, 0x53, 0x54, 0x45, 0x52, 0x10, + 0x04, 0x42, 0x08, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x53, 0x0a, 0x10, 0x66, + 0x65, 0x61, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x42, + 0x0a, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x5a, 0x33, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2d, 0x64, 0x65, + 0x76, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x67, 0x6f, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x63, 0x6f, 0x72, 0x65, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_feast_core_Store_proto_rawDescOnce sync.Once + file_feast_core_Store_proto_rawDescData = file_feast_core_Store_proto_rawDesc +) + +func file_feast_core_Store_proto_rawDescGZIP() []byte { + file_feast_core_Store_proto_rawDescOnce.Do(func() { + file_feast_core_Store_proto_rawDescData = protoimpl.X.CompressGZIP(file_feast_core_Store_proto_rawDescData) + }) + return file_feast_core_Store_proto_rawDescData +} + +var file_feast_core_Store_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_feast_core_Store_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_feast_core_Store_proto_goTypes = []interface{}{ + (Store_StoreType)(0), // 0: feast.core.Store.StoreType + (*Store)(nil), // 1: feast.core.Store + (*Store_RedisConfig)(nil), // 2: feast.core.Store.RedisConfig + (*Store_BigQueryConfig)(nil), // 3: feast.core.Store.BigQueryConfig + (*Store_CassandraConfig)(nil), // 4: feast.core.Store.CassandraConfig + (*Store_RedisClusterConfig)(nil), // 5: feast.core.Store.RedisClusterConfig + (*Store_Subscription)(nil), // 6: feast.core.Store.Subscription +} +var file_feast_core_Store_proto_depIdxs = []int32{ + 0, // 0: feast.core.Store.type:type_name -> feast.core.Store.StoreType + 6, // 1: feast.core.Store.subscriptions:type_name -> feast.core.Store.Subscription + 2, // 2: feast.core.Store.redis_config:type_name -> feast.core.Store.RedisConfig + 3, // 3: feast.core.Store.bigquery_config:type_name -> feast.core.Store.BigQueryConfig + 4, // 4: feast.core.Store.cassandra_config:type_name -> feast.core.Store.CassandraConfig + 5, // 5: feast.core.Store.redis_cluster_config:type_name -> feast.core.Store.RedisClusterConfig + 6, // [6:6] is the sub-list for method output_type + 6, // [6:6] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_feast_core_Store_proto_init() } +func file_feast_core_Store_proto_init() { + if File_feast_core_Store_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_feast_core_Store_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Store); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_Store_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Store_RedisConfig); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_Store_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Store_BigQueryConfig); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_Store_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Store_CassandraConfig); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_Store_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Store_RedisClusterConfig); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_Store_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Store_Subscription); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_feast_core_Store_proto_msgTypes[0].OneofWrappers = []interface{}{ + (*Store_RedisConfig_)(nil), + (*Store_BigqueryConfig)(nil), + (*Store_CassandraConfig_)(nil), + (*Store_RedisClusterConfig_)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_feast_core_Store_proto_rawDesc, + NumEnums: 1, + NumMessages: 6, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_feast_core_Store_proto_goTypes, + DependencyIndexes: file_feast_core_Store_proto_depIdxs, + EnumInfos: file_feast_core_Store_proto_enumTypes, + MessageInfos: file_feast_core_Store_proto_msgTypes, + }.Build() + File_feast_core_Store_proto = out.File + file_feast_core_Store_proto_rawDesc = nil + file_feast_core_Store_proto_goTypes = nil + file_feast_core_Store_proto_depIdxs = nil } diff --git a/sdk/go/protos/feast/serving/ServingService.pb.go b/sdk/go/protos/feast/serving/ServingService.pb.go index 212e8606ce7..9e5a151b7ee 100644 --- a/sdk/go/protos/feast/serving/ServingService.pb.go +++ b/sdk/go/protos/feast/serving/ServingService.pb.go @@ -1,31 +1,50 @@ +// +// Copyright 2018 The Feast Authors +// +// 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.23.0 +// protoc v3.10.0 // source: feast/serving/ServingService.proto package serving import ( context "context" - fmt "fmt" - types "github.com/gojek/feast/sdk/go/protos/feast/types" + types "github.com/feast-dev/feast/sdk/go/protos/feast/types" proto "github.com/golang/protobuf/proto" - duration "github.com/golang/protobuf/ptypes/duration" timestamp "github.com/golang/protobuf/ptypes/timestamp" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" - math "math" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" ) -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf +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 proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 type FeastServingType int32 @@ -39,24 +58,45 @@ const ( FeastServingType_FEAST_SERVING_TYPE_BATCH FeastServingType = 2 ) -var FeastServingType_name = map[int32]string{ - 0: "FEAST_SERVING_TYPE_INVALID", - 1: "FEAST_SERVING_TYPE_ONLINE", - 2: "FEAST_SERVING_TYPE_BATCH", -} +// Enum value maps for FeastServingType. +var ( + FeastServingType_name = map[int32]string{ + 0: "FEAST_SERVING_TYPE_INVALID", + 1: "FEAST_SERVING_TYPE_ONLINE", + 2: "FEAST_SERVING_TYPE_BATCH", + } + FeastServingType_value = map[string]int32{ + "FEAST_SERVING_TYPE_INVALID": 0, + "FEAST_SERVING_TYPE_ONLINE": 1, + "FEAST_SERVING_TYPE_BATCH": 2, + } +) -var FeastServingType_value = map[string]int32{ - "FEAST_SERVING_TYPE_INVALID": 0, - "FEAST_SERVING_TYPE_ONLINE": 1, - "FEAST_SERVING_TYPE_BATCH": 2, +func (x FeastServingType) Enum() *FeastServingType { + p := new(FeastServingType) + *p = x + return p } func (x FeastServingType) String() string { - return proto.EnumName(FeastServingType_name, int32(x)) + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (FeastServingType) Descriptor() protoreflect.EnumDescriptor { + return file_feast_serving_ServingService_proto_enumTypes[0].Descriptor() +} + +func (FeastServingType) Type() protoreflect.EnumType { + return &file_feast_serving_ServingService_proto_enumTypes[0] +} + +func (x FeastServingType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) } +// Deprecated: Use FeastServingType.Descriptor instead. func (FeastServingType) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_0c1ba93cf29a8d9d, []int{0} + return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{0} } type JobType int32 @@ -66,22 +106,43 @@ const ( JobType_JOB_TYPE_DOWNLOAD JobType = 1 ) -var JobType_name = map[int32]string{ - 0: "JOB_TYPE_INVALID", - 1: "JOB_TYPE_DOWNLOAD", -} +// Enum value maps for JobType. +var ( + JobType_name = map[int32]string{ + 0: "JOB_TYPE_INVALID", + 1: "JOB_TYPE_DOWNLOAD", + } + JobType_value = map[string]int32{ + "JOB_TYPE_INVALID": 0, + "JOB_TYPE_DOWNLOAD": 1, + } +) -var JobType_value = map[string]int32{ - "JOB_TYPE_INVALID": 0, - "JOB_TYPE_DOWNLOAD": 1, +func (x JobType) Enum() *JobType { + p := new(JobType) + *p = x + return p } func (x JobType) String() string { - return proto.EnumName(JobType_name, int32(x)) + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (JobType) Descriptor() protoreflect.EnumDescriptor { + return file_feast_serving_ServingService_proto_enumTypes[1].Descriptor() +} + +func (JobType) Type() protoreflect.EnumType { + return &file_feast_serving_ServingService_proto_enumTypes[1] +} + +func (x JobType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) } +// Deprecated: Use JobType.Descriptor instead. func (JobType) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_0c1ba93cf29a8d9d, []int{1} + return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{1} } type JobStatus int32 @@ -93,26 +154,47 @@ const ( JobStatus_JOB_STATUS_DONE JobStatus = 3 ) -var JobStatus_name = map[int32]string{ - 0: "JOB_STATUS_INVALID", - 1: "JOB_STATUS_PENDING", - 2: "JOB_STATUS_RUNNING", - 3: "JOB_STATUS_DONE", -} +// Enum value maps for JobStatus. +var ( + JobStatus_name = map[int32]string{ + 0: "JOB_STATUS_INVALID", + 1: "JOB_STATUS_PENDING", + 2: "JOB_STATUS_RUNNING", + 3: "JOB_STATUS_DONE", + } + JobStatus_value = map[string]int32{ + "JOB_STATUS_INVALID": 0, + "JOB_STATUS_PENDING": 1, + "JOB_STATUS_RUNNING": 2, + "JOB_STATUS_DONE": 3, + } +) -var JobStatus_value = map[string]int32{ - "JOB_STATUS_INVALID": 0, - "JOB_STATUS_PENDING": 1, - "JOB_STATUS_RUNNING": 2, - "JOB_STATUS_DONE": 3, +func (x JobStatus) Enum() *JobStatus { + p := new(JobStatus) + *p = x + return p } func (x JobStatus) String() string { - return proto.EnumName(JobStatus_name, int32(x)) + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (JobStatus) Descriptor() protoreflect.EnumDescriptor { + return file_feast_serving_ServingService_proto_enumTypes[2].Descriptor() +} + +func (JobStatus) Type() protoreflect.EnumType { + return &file_feast_serving_ServingService_proto_enumTypes[2] +} + +func (x JobStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) } +// Deprecated: Use JobStatus.Descriptor instead. func (JobStatus) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_0c1ba93cf29a8d9d, []int{2} + return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{2} } type DataFormat int32 @@ -122,56 +204,88 @@ const ( DataFormat_DATA_FORMAT_AVRO DataFormat = 1 ) -var DataFormat_name = map[int32]string{ - 0: "DATA_FORMAT_INVALID", - 1: "DATA_FORMAT_AVRO", -} +// Enum value maps for DataFormat. +var ( + DataFormat_name = map[int32]string{ + 0: "DATA_FORMAT_INVALID", + 1: "DATA_FORMAT_AVRO", + } + DataFormat_value = map[string]int32{ + "DATA_FORMAT_INVALID": 0, + "DATA_FORMAT_AVRO": 1, + } +) -var DataFormat_value = map[string]int32{ - "DATA_FORMAT_INVALID": 0, - "DATA_FORMAT_AVRO": 1, +func (x DataFormat) Enum() *DataFormat { + p := new(DataFormat) + *p = x + return p } func (x DataFormat) String() string { - return proto.EnumName(DataFormat_name, int32(x)) + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } -func (DataFormat) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_0c1ba93cf29a8d9d, []int{3} +func (DataFormat) Descriptor() protoreflect.EnumDescriptor { + return file_feast_serving_ServingService_proto_enumTypes[3].Descriptor() } -type GetFeastServingInfoRequest struct { - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` +func (DataFormat) Type() protoreflect.EnumType { + return &file_feast_serving_ServingService_proto_enumTypes[3] } -func (m *GetFeastServingInfoRequest) Reset() { *m = GetFeastServingInfoRequest{} } -func (m *GetFeastServingInfoRequest) String() string { return proto.CompactTextString(m) } -func (*GetFeastServingInfoRequest) ProtoMessage() {} -func (*GetFeastServingInfoRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0c1ba93cf29a8d9d, []int{0} +func (x DataFormat) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) } -func (m *GetFeastServingInfoRequest) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_GetFeastServingInfoRequest.Unmarshal(m, b) +// Deprecated: Use DataFormat.Descriptor instead. +func (DataFormat) EnumDescriptor() ([]byte, []int) { + return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{3} } -func (m *GetFeastServingInfoRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_GetFeastServingInfoRequest.Marshal(b, m, deterministic) + +type GetFeastServingInfoRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } -func (m *GetFeastServingInfoRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_GetFeastServingInfoRequest.Merge(m, src) + +func (x *GetFeastServingInfoRequest) Reset() { + *x = GetFeastServingInfoRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_serving_ServingService_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *GetFeastServingInfoRequest) XXX_Size() int { - return xxx_messageInfo_GetFeastServingInfoRequest.Size(m) + +func (x *GetFeastServingInfoRequest) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *GetFeastServingInfoRequest) XXX_DiscardUnknown() { - xxx_messageInfo_GetFeastServingInfoRequest.DiscardUnknown(m) + +func (*GetFeastServingInfoRequest) ProtoMessage() {} + +func (x *GetFeastServingInfoRequest) ProtoReflect() protoreflect.Message { + mi := &file_feast_serving_ServingService_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_GetFeastServingInfoRequest proto.InternalMessageInfo +// Deprecated: Use GetFeastServingInfoRequest.ProtoReflect.Descriptor instead. +func (*GetFeastServingInfoRequest) Descriptor() ([]byte, []int) { + return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{0} +} type GetFeastServingInfoResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Feast version of this serving deployment. Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` // Type of serving deployment, either ONLINE or BATCH. Different store types support different @@ -179,130 +293,135 @@ type GetFeastServingInfoResponse struct { Type FeastServingType `protobuf:"varint,2,opt,name=type,proto3,enum=feast.serving.FeastServingType" json:"type,omitempty"` // Note: Batch specific options start from 10. // Staging location for this serving store, if any. - JobStagingLocation string `protobuf:"bytes,10,opt,name=job_staging_location,json=jobStagingLocation,proto3" json:"job_staging_location,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + JobStagingLocation string `protobuf:"bytes,10,opt,name=job_staging_location,json=jobStagingLocation,proto3" json:"job_staging_location,omitempty"` } -func (m *GetFeastServingInfoResponse) Reset() { *m = GetFeastServingInfoResponse{} } -func (m *GetFeastServingInfoResponse) String() string { return proto.CompactTextString(m) } -func (*GetFeastServingInfoResponse) ProtoMessage() {} -func (*GetFeastServingInfoResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_0c1ba93cf29a8d9d, []int{1} +func (x *GetFeastServingInfoResponse) Reset() { + *x = GetFeastServingInfoResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_serving_ServingService_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *GetFeastServingInfoResponse) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_GetFeastServingInfoResponse.Unmarshal(m, b) -} -func (m *GetFeastServingInfoResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_GetFeastServingInfoResponse.Marshal(b, m, deterministic) -} -func (m *GetFeastServingInfoResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_GetFeastServingInfoResponse.Merge(m, src) +func (x *GetFeastServingInfoResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *GetFeastServingInfoResponse) XXX_Size() int { - return xxx_messageInfo_GetFeastServingInfoResponse.Size(m) -} -func (m *GetFeastServingInfoResponse) XXX_DiscardUnknown() { - xxx_messageInfo_GetFeastServingInfoResponse.DiscardUnknown(m) + +func (*GetFeastServingInfoResponse) ProtoMessage() {} + +func (x *GetFeastServingInfoResponse) ProtoReflect() protoreflect.Message { + mi := &file_feast_serving_ServingService_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_GetFeastServingInfoResponse proto.InternalMessageInfo +// Deprecated: Use GetFeastServingInfoResponse.ProtoReflect.Descriptor instead. +func (*GetFeastServingInfoResponse) Descriptor() ([]byte, []int) { + return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{1} +} -func (m *GetFeastServingInfoResponse) GetVersion() string { - if m != nil { - return m.Version +func (x *GetFeastServingInfoResponse) GetVersion() string { + if x != nil { + return x.Version } return "" } -func (m *GetFeastServingInfoResponse) GetType() FeastServingType { - if m != nil { - return m.Type +func (x *GetFeastServingInfoResponse) GetType() FeastServingType { + if x != nil { + return x.Type } return FeastServingType_FEAST_SERVING_TYPE_INVALID } -func (m *GetFeastServingInfoResponse) GetJobStagingLocation() string { - if m != nil { - return m.JobStagingLocation +func (x *GetFeastServingInfoResponse) GetJobStagingLocation() string { + if x != nil { + return x.JobStagingLocation } return "" } type FeatureReference struct { - // Project name + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Project name. This field is optional, if unspecified will default to 'default'. Project string `protobuf:"bytes,1,opt,name=project,proto3" json:"project,omitempty"` // Feature name Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` - // Feature version - Version int32 `protobuf:"varint,3,opt,name=version,proto3" json:"version,omitempty"` - // The features will be retrieved if: - // entity_timestamp - max_age <= event_timestamp <= entity_timestamp - // - // If unspecified the default max_age specified in FeatureSetSpec will - // be used. - MaxAge *duration.Duration `protobuf:"bytes,4,opt,name=max_age,json=maxAge,proto3" json:"max_age,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *FeatureReference) Reset() { *m = FeatureReference{} } -func (m *FeatureReference) String() string { return proto.CompactTextString(m) } -func (*FeatureReference) ProtoMessage() {} -func (*FeatureReference) Descriptor() ([]byte, []int) { - return fileDescriptor_0c1ba93cf29a8d9d, []int{2} + // Feature set name specifying the feature set of this referenced feature. + // This field is optional if the feature referenced is unique across the project + // in which case the feature set would be automatically infered + FeatureSet string `protobuf:"bytes,5,opt,name=feature_set,json=featureSet,proto3" json:"feature_set,omitempty"` +} + +func (x *FeatureReference) Reset() { + *x = FeatureReference{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_serving_ServingService_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *FeatureReference) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_FeatureReference.Unmarshal(m, b) -} -func (m *FeatureReference) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_FeatureReference.Marshal(b, m, deterministic) -} -func (m *FeatureReference) XXX_Merge(src proto.Message) { - xxx_messageInfo_FeatureReference.Merge(m, src) -} -func (m *FeatureReference) XXX_Size() int { - return xxx_messageInfo_FeatureReference.Size(m) -} -func (m *FeatureReference) XXX_DiscardUnknown() { - xxx_messageInfo_FeatureReference.DiscardUnknown(m) +func (x *FeatureReference) String() string { + return protoimpl.X.MessageStringOf(x) } -var xxx_messageInfo_FeatureReference proto.InternalMessageInfo +func (*FeatureReference) ProtoMessage() {} -func (m *FeatureReference) GetProject() string { - if m != nil { - return m.Project +func (x *FeatureReference) ProtoReflect() protoreflect.Message { + mi := &file_feast_serving_ServingService_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return "" + return mi.MessageOf(x) } -func (m *FeatureReference) GetName() string { - if m != nil { - return m.Name +// Deprecated: Use FeatureReference.ProtoReflect.Descriptor instead. +func (*FeatureReference) Descriptor() ([]byte, []int) { + return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{2} +} + +func (x *FeatureReference) GetProject() string { + if x != nil { + return x.Project } return "" } -func (m *FeatureReference) GetVersion() int32 { - if m != nil { - return m.Version +func (x *FeatureReference) GetName() string { + if x != nil { + return x.Name } - return 0 + return "" } -func (m *FeatureReference) GetMaxAge() *duration.Duration { - if m != nil { - return m.MaxAge +func (x *FeatureReference) GetFeatureSet() string { + if x != nil { + return x.FeatureSet } - return nil + return "" } type GetOnlineFeaturesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // List of features that are being retrieved Features []*FeatureReference `protobuf:"bytes,4,rep,name=features,proto3" json:"features,omitempty"` // List of entity rows, containing entity id and timestamp data. @@ -311,357 +430,314 @@ type GetOnlineFeaturesRequest struct { EntityRows []*GetOnlineFeaturesRequest_EntityRow `protobuf:"bytes,2,rep,name=entity_rows,json=entityRows,proto3" json:"entity_rows,omitempty"` // Option to omit entities from the response. If true, only feature // values will be returned. - OmitEntitiesInResponse bool `protobuf:"varint,3,opt,name=omit_entities_in_response,json=omitEntitiesInResponse,proto3" json:"omit_entities_in_response,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + OmitEntitiesInResponse bool `protobuf:"varint,3,opt,name=omit_entities_in_response,json=omitEntitiesInResponse,proto3" json:"omit_entities_in_response,omitempty"` } -func (m *GetOnlineFeaturesRequest) Reset() { *m = GetOnlineFeaturesRequest{} } -func (m *GetOnlineFeaturesRequest) String() string { return proto.CompactTextString(m) } -func (*GetOnlineFeaturesRequest) ProtoMessage() {} -func (*GetOnlineFeaturesRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0c1ba93cf29a8d9d, []int{3} +func (x *GetOnlineFeaturesRequest) Reset() { + *x = GetOnlineFeaturesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_serving_ServingService_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *GetOnlineFeaturesRequest) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_GetOnlineFeaturesRequest.Unmarshal(m, b) -} -func (m *GetOnlineFeaturesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_GetOnlineFeaturesRequest.Marshal(b, m, deterministic) -} -func (m *GetOnlineFeaturesRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_GetOnlineFeaturesRequest.Merge(m, src) -} -func (m *GetOnlineFeaturesRequest) XXX_Size() int { - return xxx_messageInfo_GetOnlineFeaturesRequest.Size(m) -} -func (m *GetOnlineFeaturesRequest) XXX_DiscardUnknown() { - xxx_messageInfo_GetOnlineFeaturesRequest.DiscardUnknown(m) +func (x *GetOnlineFeaturesRequest) String() string { + return protoimpl.X.MessageStringOf(x) } -var xxx_messageInfo_GetOnlineFeaturesRequest proto.InternalMessageInfo +func (*GetOnlineFeaturesRequest) ProtoMessage() {} -func (m *GetOnlineFeaturesRequest) GetFeatures() []*FeatureReference { - if m != nil { - return m.Features +func (x *GetOnlineFeaturesRequest) ProtoReflect() protoreflect.Message { + mi := &file_feast_serving_ServingService_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return nil + return mi.MessageOf(x) } -func (m *GetOnlineFeaturesRequest) GetEntityRows() []*GetOnlineFeaturesRequest_EntityRow { - if m != nil { - return m.EntityRows - } - return nil +// Deprecated: Use GetOnlineFeaturesRequest.ProtoReflect.Descriptor instead. +func (*GetOnlineFeaturesRequest) Descriptor() ([]byte, []int) { + return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{3} } -func (m *GetOnlineFeaturesRequest) GetOmitEntitiesInResponse() bool { - if m != nil { - return m.OmitEntitiesInResponse +func (x *GetOnlineFeaturesRequest) GetFeatures() []*FeatureReference { + if x != nil { + return x.Features } - return false -} - -type GetOnlineFeaturesRequest_EntityRow struct { - // Request timestamp of this row. This value will be used, together with maxAge, - // to determine feature staleness. - EntityTimestamp *timestamp.Timestamp `protobuf:"bytes,1,opt,name=entity_timestamp,json=entityTimestamp,proto3" json:"entity_timestamp,omitempty"` - // Map containing mapping of entity name to entity value. - Fields map[string]*types.Value `protobuf:"bytes,2,rep,name=fields,proto3" json:"fields,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *GetOnlineFeaturesRequest_EntityRow) Reset() { *m = GetOnlineFeaturesRequest_EntityRow{} } -func (m *GetOnlineFeaturesRequest_EntityRow) String() string { return proto.CompactTextString(m) } -func (*GetOnlineFeaturesRequest_EntityRow) ProtoMessage() {} -func (*GetOnlineFeaturesRequest_EntityRow) Descriptor() ([]byte, []int) { - return fileDescriptor_0c1ba93cf29a8d9d, []int{3, 0} -} - -func (m *GetOnlineFeaturesRequest_EntityRow) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_GetOnlineFeaturesRequest_EntityRow.Unmarshal(m, b) -} -func (m *GetOnlineFeaturesRequest_EntityRow) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_GetOnlineFeaturesRequest_EntityRow.Marshal(b, m, deterministic) -} -func (m *GetOnlineFeaturesRequest_EntityRow) XXX_Merge(src proto.Message) { - xxx_messageInfo_GetOnlineFeaturesRequest_EntityRow.Merge(m, src) -} -func (m *GetOnlineFeaturesRequest_EntityRow) XXX_Size() int { - return xxx_messageInfo_GetOnlineFeaturesRequest_EntityRow.Size(m) -} -func (m *GetOnlineFeaturesRequest_EntityRow) XXX_DiscardUnknown() { - xxx_messageInfo_GetOnlineFeaturesRequest_EntityRow.DiscardUnknown(m) + return nil } -var xxx_messageInfo_GetOnlineFeaturesRequest_EntityRow proto.InternalMessageInfo - -func (m *GetOnlineFeaturesRequest_EntityRow) GetEntityTimestamp() *timestamp.Timestamp { - if m != nil { - return m.EntityTimestamp +func (x *GetOnlineFeaturesRequest) GetEntityRows() []*GetOnlineFeaturesRequest_EntityRow { + if x != nil { + return x.EntityRows } return nil } -func (m *GetOnlineFeaturesRequest_EntityRow) GetFields() map[string]*types.Value { - if m != nil { - return m.Fields +func (x *GetOnlineFeaturesRequest) GetOmitEntitiesInResponse() bool { + if x != nil { + return x.OmitEntitiesInResponse } - return nil + return false } type GetBatchFeaturesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // List of features that are being retrieved Features []*FeatureReference `protobuf:"bytes,3,rep,name=features,proto3" json:"features,omitempty"` // Source of the entity dataset containing the timestamps and entity keys to retrieve // features for. - DatasetSource *DatasetSource `protobuf:"bytes,2,opt,name=dataset_source,json=datasetSource,proto3" json:"dataset_source,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + DatasetSource *DatasetSource `protobuf:"bytes,2,opt,name=dataset_source,json=datasetSource,proto3" json:"dataset_source,omitempty"` } -func (m *GetBatchFeaturesRequest) Reset() { *m = GetBatchFeaturesRequest{} } -func (m *GetBatchFeaturesRequest) String() string { return proto.CompactTextString(m) } -func (*GetBatchFeaturesRequest) ProtoMessage() {} -func (*GetBatchFeaturesRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0c1ba93cf29a8d9d, []int{4} +func (x *GetBatchFeaturesRequest) Reset() { + *x = GetBatchFeaturesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_serving_ServingService_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *GetBatchFeaturesRequest) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_GetBatchFeaturesRequest.Unmarshal(m, b) -} -func (m *GetBatchFeaturesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_GetBatchFeaturesRequest.Marshal(b, m, deterministic) +func (x *GetBatchFeaturesRequest) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *GetBatchFeaturesRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_GetBatchFeaturesRequest.Merge(m, src) -} -func (m *GetBatchFeaturesRequest) XXX_Size() int { - return xxx_messageInfo_GetBatchFeaturesRequest.Size(m) -} -func (m *GetBatchFeaturesRequest) XXX_DiscardUnknown() { - xxx_messageInfo_GetBatchFeaturesRequest.DiscardUnknown(m) + +func (*GetBatchFeaturesRequest) ProtoMessage() {} + +func (x *GetBatchFeaturesRequest) ProtoReflect() protoreflect.Message { + mi := &file_feast_serving_ServingService_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_GetBatchFeaturesRequest proto.InternalMessageInfo +// Deprecated: Use GetBatchFeaturesRequest.ProtoReflect.Descriptor instead. +func (*GetBatchFeaturesRequest) Descriptor() ([]byte, []int) { + return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{4} +} -func (m *GetBatchFeaturesRequest) GetFeatures() []*FeatureReference { - if m != nil { - return m.Features +func (x *GetBatchFeaturesRequest) GetFeatures() []*FeatureReference { + if x != nil { + return x.Features } return nil } -func (m *GetBatchFeaturesRequest) GetDatasetSource() *DatasetSource { - if m != nil { - return m.DatasetSource +func (x *GetBatchFeaturesRequest) GetDatasetSource() *DatasetSource { + if x != nil { + return x.DatasetSource } return nil } type GetOnlineFeaturesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Feature values retrieved from feast. - FieldValues []*GetOnlineFeaturesResponse_FieldValues `protobuf:"bytes,1,rep,name=field_values,json=fieldValues,proto3" json:"field_values,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + FieldValues []*GetOnlineFeaturesResponse_FieldValues `protobuf:"bytes,1,rep,name=field_values,json=fieldValues,proto3" json:"field_values,omitempty"` } -func (m *GetOnlineFeaturesResponse) Reset() { *m = GetOnlineFeaturesResponse{} } -func (m *GetOnlineFeaturesResponse) String() string { return proto.CompactTextString(m) } -func (*GetOnlineFeaturesResponse) ProtoMessage() {} -func (*GetOnlineFeaturesResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_0c1ba93cf29a8d9d, []int{5} +func (x *GetOnlineFeaturesResponse) Reset() { + *x = GetOnlineFeaturesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_serving_ServingService_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *GetOnlineFeaturesResponse) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_GetOnlineFeaturesResponse.Unmarshal(m, b) -} -func (m *GetOnlineFeaturesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_GetOnlineFeaturesResponse.Marshal(b, m, deterministic) -} -func (m *GetOnlineFeaturesResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_GetOnlineFeaturesResponse.Merge(m, src) -} -func (m *GetOnlineFeaturesResponse) XXX_Size() int { - return xxx_messageInfo_GetOnlineFeaturesResponse.Size(m) -} -func (m *GetOnlineFeaturesResponse) XXX_DiscardUnknown() { - xxx_messageInfo_GetOnlineFeaturesResponse.DiscardUnknown(m) +func (x *GetOnlineFeaturesResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -var xxx_messageInfo_GetOnlineFeaturesResponse proto.InternalMessageInfo +func (*GetOnlineFeaturesResponse) ProtoMessage() {} -func (m *GetOnlineFeaturesResponse) GetFieldValues() []*GetOnlineFeaturesResponse_FieldValues { - if m != nil { - return m.FieldValues +func (x *GetOnlineFeaturesResponse) ProtoReflect() protoreflect.Message { + mi := &file_feast_serving_ServingService_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return nil + return mi.MessageOf(x) } -type GetOnlineFeaturesResponse_FieldValues struct { - // Map of feature or entity name to feature/entity values. - // Timestamps are not returned in this response. - Fields map[string]*types.Value `protobuf:"bytes,1,rep,name=fields,proto3" json:"fields,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *GetOnlineFeaturesResponse_FieldValues) Reset() { *m = GetOnlineFeaturesResponse_FieldValues{} } -func (m *GetOnlineFeaturesResponse_FieldValues) String() string { return proto.CompactTextString(m) } -func (*GetOnlineFeaturesResponse_FieldValues) ProtoMessage() {} -func (*GetOnlineFeaturesResponse_FieldValues) Descriptor() ([]byte, []int) { - return fileDescriptor_0c1ba93cf29a8d9d, []int{5, 0} -} - -func (m *GetOnlineFeaturesResponse_FieldValues) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_GetOnlineFeaturesResponse_FieldValues.Unmarshal(m, b) -} -func (m *GetOnlineFeaturesResponse_FieldValues) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_GetOnlineFeaturesResponse_FieldValues.Marshal(b, m, deterministic) -} -func (m *GetOnlineFeaturesResponse_FieldValues) XXX_Merge(src proto.Message) { - xxx_messageInfo_GetOnlineFeaturesResponse_FieldValues.Merge(m, src) -} -func (m *GetOnlineFeaturesResponse_FieldValues) XXX_Size() int { - return xxx_messageInfo_GetOnlineFeaturesResponse_FieldValues.Size(m) -} -func (m *GetOnlineFeaturesResponse_FieldValues) XXX_DiscardUnknown() { - xxx_messageInfo_GetOnlineFeaturesResponse_FieldValues.DiscardUnknown(m) +// Deprecated: Use GetOnlineFeaturesResponse.ProtoReflect.Descriptor instead. +func (*GetOnlineFeaturesResponse) Descriptor() ([]byte, []int) { + return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{5} } -var xxx_messageInfo_GetOnlineFeaturesResponse_FieldValues proto.InternalMessageInfo - -func (m *GetOnlineFeaturesResponse_FieldValues) GetFields() map[string]*types.Value { - if m != nil { - return m.Fields +func (x *GetOnlineFeaturesResponse) GetFieldValues() []*GetOnlineFeaturesResponse_FieldValues { + if x != nil { + return x.FieldValues } return nil } type GetBatchFeaturesResponse struct { - Job *Job `protobuf:"bytes,1,opt,name=job,proto3" json:"job,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields -func (m *GetBatchFeaturesResponse) Reset() { *m = GetBatchFeaturesResponse{} } -func (m *GetBatchFeaturesResponse) String() string { return proto.CompactTextString(m) } -func (*GetBatchFeaturesResponse) ProtoMessage() {} -func (*GetBatchFeaturesResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_0c1ba93cf29a8d9d, []int{6} + Job *Job `protobuf:"bytes,1,opt,name=job,proto3" json:"job,omitempty"` } -func (m *GetBatchFeaturesResponse) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_GetBatchFeaturesResponse.Unmarshal(m, b) -} -func (m *GetBatchFeaturesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_GetBatchFeaturesResponse.Marshal(b, m, deterministic) -} -func (m *GetBatchFeaturesResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_GetBatchFeaturesResponse.Merge(m, src) +func (x *GetBatchFeaturesResponse) Reset() { + *x = GetBatchFeaturesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_serving_ServingService_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *GetBatchFeaturesResponse) XXX_Size() int { - return xxx_messageInfo_GetBatchFeaturesResponse.Size(m) + +func (x *GetBatchFeaturesResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *GetBatchFeaturesResponse) XXX_DiscardUnknown() { - xxx_messageInfo_GetBatchFeaturesResponse.DiscardUnknown(m) + +func (*GetBatchFeaturesResponse) ProtoMessage() {} + +func (x *GetBatchFeaturesResponse) ProtoReflect() protoreflect.Message { + mi := &file_feast_serving_ServingService_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_GetBatchFeaturesResponse proto.InternalMessageInfo +// Deprecated: Use GetBatchFeaturesResponse.ProtoReflect.Descriptor instead. +func (*GetBatchFeaturesResponse) Descriptor() ([]byte, []int) { + return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{6} +} -func (m *GetBatchFeaturesResponse) GetJob() *Job { - if m != nil { - return m.Job +func (x *GetBatchFeaturesResponse) GetJob() *Job { + if x != nil { + return x.Job } return nil } type GetJobRequest struct { - Job *Job `protobuf:"bytes,1,opt,name=job,proto3" json:"job,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields -func (m *GetJobRequest) Reset() { *m = GetJobRequest{} } -func (m *GetJobRequest) String() string { return proto.CompactTextString(m) } -func (*GetJobRequest) ProtoMessage() {} -func (*GetJobRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_0c1ba93cf29a8d9d, []int{7} + Job *Job `protobuf:"bytes,1,opt,name=job,proto3" json:"job,omitempty"` } -func (m *GetJobRequest) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_GetJobRequest.Unmarshal(m, b) -} -func (m *GetJobRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_GetJobRequest.Marshal(b, m, deterministic) -} -func (m *GetJobRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_GetJobRequest.Merge(m, src) +func (x *GetJobRequest) Reset() { + *x = GetJobRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_serving_ServingService_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *GetJobRequest) XXX_Size() int { - return xxx_messageInfo_GetJobRequest.Size(m) + +func (x *GetJobRequest) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *GetJobRequest) XXX_DiscardUnknown() { - xxx_messageInfo_GetJobRequest.DiscardUnknown(m) + +func (*GetJobRequest) ProtoMessage() {} + +func (x *GetJobRequest) ProtoReflect() protoreflect.Message { + mi := &file_feast_serving_ServingService_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_GetJobRequest proto.InternalMessageInfo +// Deprecated: Use GetJobRequest.ProtoReflect.Descriptor instead. +func (*GetJobRequest) Descriptor() ([]byte, []int) { + return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{7} +} -func (m *GetJobRequest) GetJob() *Job { - if m != nil { - return m.Job +func (x *GetJobRequest) GetJob() *Job { + if x != nil { + return x.Job } return nil } type GetJobResponse struct { - Job *Job `protobuf:"bytes,1,opt,name=job,proto3" json:"job,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields -func (m *GetJobResponse) Reset() { *m = GetJobResponse{} } -func (m *GetJobResponse) String() string { return proto.CompactTextString(m) } -func (*GetJobResponse) ProtoMessage() {} -func (*GetJobResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_0c1ba93cf29a8d9d, []int{8} + Job *Job `protobuf:"bytes,1,opt,name=job,proto3" json:"job,omitempty"` } -func (m *GetJobResponse) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_GetJobResponse.Unmarshal(m, b) -} -func (m *GetJobResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_GetJobResponse.Marshal(b, m, deterministic) -} -func (m *GetJobResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_GetJobResponse.Merge(m, src) +func (x *GetJobResponse) Reset() { + *x = GetJobResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_serving_ServingService_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *GetJobResponse) XXX_Size() int { - return xxx_messageInfo_GetJobResponse.Size(m) + +func (x *GetJobResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *GetJobResponse) XXX_DiscardUnknown() { - xxx_messageInfo_GetJobResponse.DiscardUnknown(m) + +func (*GetJobResponse) ProtoMessage() {} + +func (x *GetJobResponse) ProtoReflect() protoreflect.Message { + mi := &file_feast_serving_ServingService_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_GetJobResponse proto.InternalMessageInfo +// Deprecated: Use GetJobResponse.ProtoReflect.Descriptor instead. +func (*GetJobResponse) Descriptor() ([]byte, []int) { + return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{8} +} -func (m *GetJobResponse) GetJob() *Job { - if m != nil { - return m.Job +func (x *GetJobResponse) GetJob() *Job { + if x != nil { + return x.Job } return nil } type Job struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // Output only. The type of the job. Type JobType `protobuf:"varint,2,opt,name=type,proto3,enum=feast.serving.JobType" json:"type,omitempty"` @@ -674,300 +750,777 @@ type Job struct { FileUris []string `protobuf:"bytes,5,rep,name=file_uris,json=fileUris,proto3" json:"file_uris,omitempty"` // Output only. The data format for all the files. // For CSV format, the files contain both feature values and a column header. - DataFormat DataFormat `protobuf:"varint,6,opt,name=data_format,json=dataFormat,proto3,enum=feast.serving.DataFormat" json:"data_format,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + DataFormat DataFormat `protobuf:"varint,6,opt,name=data_format,json=dataFormat,proto3,enum=feast.serving.DataFormat" json:"data_format,omitempty"` } -func (m *Job) Reset() { *m = Job{} } -func (m *Job) String() string { return proto.CompactTextString(m) } -func (*Job) ProtoMessage() {} -func (*Job) Descriptor() ([]byte, []int) { - return fileDescriptor_0c1ba93cf29a8d9d, []int{9} +func (x *Job) Reset() { + *x = Job{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_serving_ServingService_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *Job) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Job.Unmarshal(m, b) -} -func (m *Job) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Job.Marshal(b, m, deterministic) -} -func (m *Job) XXX_Merge(src proto.Message) { - xxx_messageInfo_Job.Merge(m, src) -} -func (m *Job) XXX_Size() int { - return xxx_messageInfo_Job.Size(m) +func (x *Job) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *Job) XXX_DiscardUnknown() { - xxx_messageInfo_Job.DiscardUnknown(m) + +func (*Job) ProtoMessage() {} + +func (x *Job) ProtoReflect() protoreflect.Message { + mi := &file_feast_serving_ServingService_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_Job proto.InternalMessageInfo +// Deprecated: Use Job.ProtoReflect.Descriptor instead. +func (*Job) Descriptor() ([]byte, []int) { + return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{9} +} -func (m *Job) GetId() string { - if m != nil { - return m.Id +func (x *Job) GetId() string { + if x != nil { + return x.Id } return "" } -func (m *Job) GetType() JobType { - if m != nil { - return m.Type +func (x *Job) GetType() JobType { + if x != nil { + return x.Type } return JobType_JOB_TYPE_INVALID } -func (m *Job) GetStatus() JobStatus { - if m != nil { - return m.Status +func (x *Job) GetStatus() JobStatus { + if x != nil { + return x.Status } return JobStatus_JOB_STATUS_INVALID } -func (m *Job) GetError() string { - if m != nil { - return m.Error +func (x *Job) GetError() string { + if x != nil { + return x.Error } return "" } -func (m *Job) GetFileUris() []string { - if m != nil { - return m.FileUris +func (x *Job) GetFileUris() []string { + if x != nil { + return x.FileUris } return nil } -func (m *Job) GetDataFormat() DataFormat { - if m != nil { - return m.DataFormat +func (x *Job) GetDataFormat() DataFormat { + if x != nil { + return x.DataFormat } return DataFormat_DATA_FORMAT_INVALID } type DatasetSource struct { - // Types that are valid to be assigned to DatasetSource: + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to DatasetSource: // *DatasetSource_FileSource_ - DatasetSource isDatasetSource_DatasetSource `protobuf_oneof:"dataset_source"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + DatasetSource isDatasetSource_DatasetSource `protobuf_oneof:"dataset_source"` } -func (m *DatasetSource) Reset() { *m = DatasetSource{} } -func (m *DatasetSource) String() string { return proto.CompactTextString(m) } -func (*DatasetSource) ProtoMessage() {} -func (*DatasetSource) Descriptor() ([]byte, []int) { - return fileDescriptor_0c1ba93cf29a8d9d, []int{10} +func (x *DatasetSource) Reset() { + *x = DatasetSource{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_serving_ServingService_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *DatasetSource) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_DatasetSource.Unmarshal(m, b) -} -func (m *DatasetSource) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_DatasetSource.Marshal(b, m, deterministic) +func (x *DatasetSource) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *DatasetSource) XXX_Merge(src proto.Message) { - xxx_messageInfo_DatasetSource.Merge(m, src) + +func (*DatasetSource) ProtoMessage() {} + +func (x *DatasetSource) ProtoReflect() protoreflect.Message { + mi := &file_feast_serving_ServingService_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -func (m *DatasetSource) XXX_Size() int { - return xxx_messageInfo_DatasetSource.Size(m) + +// Deprecated: Use DatasetSource.ProtoReflect.Descriptor instead. +func (*DatasetSource) Descriptor() ([]byte, []int) { + return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{10} } -func (m *DatasetSource) XXX_DiscardUnknown() { - xxx_messageInfo_DatasetSource.DiscardUnknown(m) + +func (m *DatasetSource) GetDatasetSource() isDatasetSource_DatasetSource { + if m != nil { + return m.DatasetSource + } + return nil } -var xxx_messageInfo_DatasetSource proto.InternalMessageInfo +func (x *DatasetSource) GetFileSource() *DatasetSource_FileSource { + if x, ok := x.GetDatasetSource().(*DatasetSource_FileSource_); ok { + return x.FileSource + } + return nil +} type isDatasetSource_DatasetSource interface { isDatasetSource_DatasetSource() } type DatasetSource_FileSource_ struct { + // File source to load the dataset from. FileSource *DatasetSource_FileSource `protobuf:"bytes,1,opt,name=file_source,json=fileSource,proto3,oneof"` } func (*DatasetSource_FileSource_) isDatasetSource_DatasetSource() {} -func (m *DatasetSource) GetDatasetSource() isDatasetSource_DatasetSource { - if m != nil { - return m.DatasetSource +type GetOnlineFeaturesRequest_EntityRow struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Request timestamp of this row. This value will be used, together with maxAge, + // to determine feature staleness. + EntityTimestamp *timestamp.Timestamp `protobuf:"bytes,1,opt,name=entity_timestamp,json=entityTimestamp,proto3" json:"entity_timestamp,omitempty"` + // Map containing mapping of entity name to entity value. + Fields map[string]*types.Value `protobuf:"bytes,2,rep,name=fields,proto3" json:"fields,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *GetOnlineFeaturesRequest_EntityRow) Reset() { + *x = GetOnlineFeaturesRequest_EntityRow{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_serving_ServingService_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetOnlineFeaturesRequest_EntityRow) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetOnlineFeaturesRequest_EntityRow) ProtoMessage() {} + +func (x *GetOnlineFeaturesRequest_EntityRow) ProtoReflect() protoreflect.Message { + mi := &file_feast_serving_ServingService_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetOnlineFeaturesRequest_EntityRow.ProtoReflect.Descriptor instead. +func (*GetOnlineFeaturesRequest_EntityRow) Descriptor() ([]byte, []int) { + return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{3, 0} +} + +func (x *GetOnlineFeaturesRequest_EntityRow) GetEntityTimestamp() *timestamp.Timestamp { + if x != nil { + return x.EntityTimestamp } return nil } -func (m *DatasetSource) GetFileSource() *DatasetSource_FileSource { - if x, ok := m.GetDatasetSource().(*DatasetSource_FileSource_); ok { - return x.FileSource +func (x *GetOnlineFeaturesRequest_EntityRow) GetFields() map[string]*types.Value { + if x != nil { + return x.Fields } return nil } -// XXX_OneofWrappers is for the internal use of the proto package. -func (*DatasetSource) XXX_OneofWrappers() []interface{} { - return []interface{}{ - (*DatasetSource_FileSource_)(nil), +type GetOnlineFeaturesResponse_FieldValues struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Map of feature or entity name to feature/entity values. + // Timestamps are not returned in this response. + Fields map[string]*types.Value `protobuf:"bytes,1,rep,name=fields,proto3" json:"fields,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *GetOnlineFeaturesResponse_FieldValues) Reset() { + *x = GetOnlineFeaturesResponse_FieldValues{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_serving_ServingService_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetOnlineFeaturesResponse_FieldValues) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetOnlineFeaturesResponse_FieldValues) ProtoMessage() {} + +func (x *GetOnlineFeaturesResponse_FieldValues) ProtoReflect() protoreflect.Message { + mi := &file_feast_serving_ServingService_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } + return mi.MessageOf(x) +} + +// Deprecated: Use GetOnlineFeaturesResponse_FieldValues.ProtoReflect.Descriptor instead. +func (*GetOnlineFeaturesResponse_FieldValues) Descriptor() ([]byte, []int) { + return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{5, 0} +} + +func (x *GetOnlineFeaturesResponse_FieldValues) GetFields() map[string]*types.Value { + if x != nil { + return x.Fields + } + return nil } type DatasetSource_FileSource struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // URIs to retrieve the dataset from, e.g. gs://bucket/directory/object.csv. Wildcards are // supported. This data must be compatible to be uploaded to the serving store, and also be // accessible by this serving instance. FileUris []string `protobuf:"bytes,1,rep,name=file_uris,json=fileUris,proto3" json:"file_uris,omitempty"` // Format of the data. Currently only avro is supported. - DataFormat DataFormat `protobuf:"varint,2,opt,name=data_format,json=dataFormat,proto3,enum=feast.serving.DataFormat" json:"data_format,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + DataFormat DataFormat `protobuf:"varint,2,opt,name=data_format,json=dataFormat,proto3,enum=feast.serving.DataFormat" json:"data_format,omitempty"` } -func (m *DatasetSource_FileSource) Reset() { *m = DatasetSource_FileSource{} } -func (m *DatasetSource_FileSource) String() string { return proto.CompactTextString(m) } -func (*DatasetSource_FileSource) ProtoMessage() {} -func (*DatasetSource_FileSource) Descriptor() ([]byte, []int) { - return fileDescriptor_0c1ba93cf29a8d9d, []int{10, 0} +func (x *DatasetSource_FileSource) Reset() { + *x = DatasetSource_FileSource{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_serving_ServingService_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *DatasetSource_FileSource) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_DatasetSource_FileSource.Unmarshal(m, b) -} -func (m *DatasetSource_FileSource) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_DatasetSource_FileSource.Marshal(b, m, deterministic) -} -func (m *DatasetSource_FileSource) XXX_Merge(src proto.Message) { - xxx_messageInfo_DatasetSource_FileSource.Merge(m, src) +func (x *DatasetSource_FileSource) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *DatasetSource_FileSource) XXX_Size() int { - return xxx_messageInfo_DatasetSource_FileSource.Size(m) -} -func (m *DatasetSource_FileSource) XXX_DiscardUnknown() { - xxx_messageInfo_DatasetSource_FileSource.DiscardUnknown(m) + +func (*DatasetSource_FileSource) ProtoMessage() {} + +func (x *DatasetSource_FileSource) ProtoReflect() protoreflect.Message { + mi := &file_feast_serving_ServingService_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_DatasetSource_FileSource proto.InternalMessageInfo +// Deprecated: Use DatasetSource_FileSource.ProtoReflect.Descriptor instead. +func (*DatasetSource_FileSource) Descriptor() ([]byte, []int) { + return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{10, 0} +} -func (m *DatasetSource_FileSource) GetFileUris() []string { - if m != nil { - return m.FileUris +func (x *DatasetSource_FileSource) GetFileUris() []string { + if x != nil { + return x.FileUris } return nil } -func (m *DatasetSource_FileSource) GetDataFormat() DataFormat { - if m != nil { - return m.DataFormat +func (x *DatasetSource_FileSource) GetDataFormat() DataFormat { + if x != nil { + return x.DataFormat } return DataFormat_DATA_FORMAT_INVALID } -func init() { - proto.RegisterEnum("feast.serving.FeastServingType", FeastServingType_name, FeastServingType_value) - proto.RegisterEnum("feast.serving.JobType", JobType_name, JobType_value) - proto.RegisterEnum("feast.serving.JobStatus", JobStatus_name, JobStatus_value) - proto.RegisterEnum("feast.serving.DataFormat", DataFormat_name, DataFormat_value) - proto.RegisterType((*GetFeastServingInfoRequest)(nil), "feast.serving.GetFeastServingInfoRequest") - proto.RegisterType((*GetFeastServingInfoResponse)(nil), "feast.serving.GetFeastServingInfoResponse") - proto.RegisterType((*FeatureReference)(nil), "feast.serving.FeatureReference") - proto.RegisterType((*GetOnlineFeaturesRequest)(nil), "feast.serving.GetOnlineFeaturesRequest") - proto.RegisterType((*GetOnlineFeaturesRequest_EntityRow)(nil), "feast.serving.GetOnlineFeaturesRequest.EntityRow") - proto.RegisterMapType((map[string]*types.Value)(nil), "feast.serving.GetOnlineFeaturesRequest.EntityRow.FieldsEntry") - proto.RegisterType((*GetBatchFeaturesRequest)(nil), "feast.serving.GetBatchFeaturesRequest") - proto.RegisterType((*GetOnlineFeaturesResponse)(nil), "feast.serving.GetOnlineFeaturesResponse") - proto.RegisterType((*GetOnlineFeaturesResponse_FieldValues)(nil), "feast.serving.GetOnlineFeaturesResponse.FieldValues") - proto.RegisterMapType((map[string]*types.Value)(nil), "feast.serving.GetOnlineFeaturesResponse.FieldValues.FieldsEntry") - proto.RegisterType((*GetBatchFeaturesResponse)(nil), "feast.serving.GetBatchFeaturesResponse") - proto.RegisterType((*GetJobRequest)(nil), "feast.serving.GetJobRequest") - proto.RegisterType((*GetJobResponse)(nil), "feast.serving.GetJobResponse") - proto.RegisterType((*Job)(nil), "feast.serving.Job") - proto.RegisterType((*DatasetSource)(nil), "feast.serving.DatasetSource") - proto.RegisterType((*DatasetSource_FileSource)(nil), "feast.serving.DatasetSource.FileSource") -} - -func init() { proto.RegisterFile("feast/serving/ServingService.proto", fileDescriptor_0c1ba93cf29a8d9d) } - -var fileDescriptor_0c1ba93cf29a8d9d = []byte{ - // 1101 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x56, 0x51, 0x73, 0xda, 0xc6, - 0x13, 0x8f, 0xc0, 0xc6, 0x66, 0xf9, 0x1b, 0x2b, 0x67, 0xff, 0x6d, 0x59, 0x71, 0x12, 0x86, 0xe9, - 0xd4, 0x94, 0x07, 0xd1, 0x92, 0x36, 0xd3, 0x26, 0xd3, 0x99, 0x40, 0x10, 0x04, 0x8f, 0x03, 0x9e, - 0x03, 0x3b, 0x6d, 0x5f, 0x34, 0x02, 0x4e, 0x58, 0x36, 0xe8, 0xa8, 0xee, 0x70, 0xe2, 0x2f, 0xd1, - 0x87, 0xbe, 0x76, 0xa6, 0xdf, 0xa0, 0xaf, 0xfd, 0x24, 0xfd, 0x02, 0xed, 0xa7, 0xe8, 0x63, 0x47, - 0xa7, 0x03, 0x23, 0xc0, 0x8e, 0xdd, 0x87, 0x3e, 0xe9, 0x6e, 0xf7, 0xb7, 0xb7, 0xb7, 0xbf, 0xdb, - 0x5d, 0x2d, 0x64, 0x1d, 0x62, 0x33, 0x5e, 0x60, 0xc4, 0xbf, 0x74, 0xbd, 0x7e, 0xa1, 0x15, 0x7e, - 0xc5, 0xa7, 0x4b, 0x8c, 0x91, 0x4f, 0x39, 0x45, 0x1b, 0x02, 0x63, 0x48, 0x8c, 0xfe, 0xb4, 0x4f, - 0x69, 0x7f, 0x40, 0x0a, 0x42, 0xd9, 0x19, 0x3b, 0x05, 0xee, 0x0e, 0x09, 0xe3, 0xf6, 0x70, 0x14, - 0xe2, 0xf5, 0x27, 0xf3, 0x80, 0xde, 0xd8, 0xb7, 0xb9, 0x4b, 0x3d, 0xa9, 0xdf, 0x0d, 0x7d, 0xf2, - 0xab, 0x11, 0x61, 0x85, 0x53, 0x7b, 0x30, 0x96, 0x8e, 0xb2, 0xfb, 0xa0, 0xd7, 0x08, 0xaf, 0x06, - 0x5a, 0x79, 0x91, 0xba, 0xe7, 0x50, 0x4c, 0x7e, 0x1c, 0x13, 0xc6, 0xb3, 0xbf, 0x2a, 0xf0, 0x68, - 0xa9, 0x9a, 0x8d, 0xa8, 0xc7, 0x08, 0xd2, 0x60, 0xed, 0x92, 0xf8, 0xcc, 0xa5, 0x9e, 0xa6, 0x64, - 0x94, 0x5c, 0x12, 0x4f, 0xb6, 0xe8, 0x19, 0xac, 0x04, 0xce, 0xb4, 0x58, 0x46, 0xc9, 0xa5, 0x8b, - 0x4f, 0x8d, 0x48, 0x3c, 0xc6, 0xec, 0x81, 0xed, 0xab, 0x11, 0xc1, 0x02, 0x8c, 0x3e, 0x87, 0xed, - 0x73, 0xda, 0xb1, 0x18, 0xb7, 0xfb, 0xae, 0xd7, 0xb7, 0x06, 0xb4, 0x2b, 0x62, 0xd0, 0x40, 0x9c, - 0x8d, 0xce, 0x69, 0xa7, 0x15, 0xaa, 0x8e, 0xa4, 0x26, 0xfb, 0x93, 0x02, 0x6a, 0x95, 0xd8, 0x7c, - 0xec, 0x13, 0x4c, 0x1c, 0xe2, 0x13, 0xaf, 0x2b, 0x6e, 0x35, 0xf2, 0xe9, 0x39, 0xe9, 0xf2, 0xc9, - 0xad, 0xe4, 0x16, 0x21, 0x58, 0xf1, 0xec, 0x61, 0x78, 0xab, 0x24, 0x16, 0xeb, 0xd9, 0x18, 0xe2, - 0x19, 0x25, 0xb7, 0x7a, 0x1d, 0x43, 0x11, 0xd6, 0x86, 0xf6, 0x07, 0xcb, 0xee, 0x13, 0x6d, 0x25, - 0xa3, 0xe4, 0x52, 0xc5, 0x3d, 0x23, 0xa4, 0xd9, 0x98, 0xd0, 0x6c, 0x54, 0x24, 0xcd, 0x38, 0x31, - 0xb4, 0x3f, 0x94, 0xfa, 0x24, 0xfb, 0x67, 0x1c, 0xb4, 0x1a, 0xe1, 0x4d, 0x6f, 0xe0, 0x7a, 0x44, - 0xde, 0x8c, 0x49, 0x3a, 0xd1, 0x4b, 0x58, 0x77, 0xa4, 0x48, 0x5b, 0xc9, 0xc4, 0x73, 0xa9, 0x65, - 0xc4, 0x44, 0x62, 0xc1, 0x53, 0x03, 0x84, 0x21, 0x45, 0x3c, 0xee, 0xf2, 0x2b, 0xcb, 0xa7, 0xef, - 0x99, 0x16, 0x13, 0xf6, 0x5f, 0xcc, 0xd9, 0xdf, 0xe4, 0xda, 0x30, 0x85, 0x29, 0xa6, 0xef, 0x31, - 0x90, 0xc9, 0x92, 0xa1, 0x6f, 0x60, 0x8f, 0x0e, 0x5d, 0x6e, 0x09, 0x91, 0x4b, 0x98, 0xe5, 0x7a, - 0x96, 0x2f, 0x1f, 0x57, 0xb0, 0xb1, 0x8e, 0x77, 0x02, 0x80, 0x29, 0xf5, 0x75, 0x6f, 0xf2, 0xf4, - 0xfa, 0xdf, 0x0a, 0x24, 0xa7, 0x87, 0x22, 0x13, 0x54, 0x79, 0xb9, 0x69, 0x66, 0x0a, 0xee, 0x53, - 0x45, 0x7d, 0x81, 0xb3, 0xf6, 0x04, 0x81, 0x37, 0x43, 0x9b, 0xa9, 0x00, 0x9d, 0x40, 0xc2, 0x71, - 0xc9, 0xa0, 0x37, 0x09, 0xef, 0xdb, 0x7b, 0x87, 0x67, 0x54, 0x85, 0xbd, 0xe9, 0x71, 0xff, 0x0a, - 0xcb, 0xc3, 0xf4, 0xb7, 0x90, 0x9a, 0x11, 0x23, 0x15, 0xe2, 0x17, 0xe4, 0x4a, 0xe6, 0x46, 0xb0, - 0x44, 0x39, 0x58, 0xbd, 0x0c, 0x8a, 0x42, 0x24, 0x46, 0xaa, 0x88, 0xa4, 0x5b, 0x51, 0x2e, 0x86, - 0x28, 0x17, 0x1c, 0x02, 0x5e, 0xc4, 0xbe, 0x56, 0xb2, 0xbf, 0x28, 0xb0, 0x5b, 0x23, 0xbc, 0x6c, - 0xf3, 0xee, 0xd9, 0x6d, 0x4f, 0x1c, 0xbf, 0xef, 0x13, 0xbf, 0x86, 0x74, 0xcf, 0xe6, 0x36, 0x23, - 0xdc, 0x62, 0x74, 0xec, 0x77, 0x27, 0xf7, 0xd9, 0x9f, 0x3b, 0xa2, 0x12, 0x82, 0x5a, 0x02, 0x83, - 0x37, 0x7a, 0xb3, 0xdb, 0xec, 0x6f, 0x31, 0xd8, 0x5b, 0xc2, 0x93, 0xac, 0xd8, 0x77, 0xf0, 0x3f, - 0x41, 0x8a, 0x25, 0xc2, 0x61, 0x9a, 0x22, 0xee, 0xf8, 0xe5, 0xc7, 0x79, 0x0e, 0xed, 0x43, 0x7a, - 0x05, 0x23, 0x0c, 0xa7, 0x9c, 0xeb, 0x8d, 0xfe, 0xbb, 0x22, 0x49, 0x0e, 0xf7, 0xe8, 0xbb, 0xe9, - 0x53, 0x86, 0x2e, 0x5e, 0xfd, 0x1b, 0x17, 0xff, 0xc5, 0x6b, 0xbe, 0x12, 0x05, 0x3b, 0xf7, 0x98, - 0x92, 0xad, 0x4f, 0x20, 0x7e, 0x4e, 0x3b, 0x32, 0x93, 0xd1, 0x5c, 0x04, 0x87, 0xb4, 0x83, 0x03, - 0x75, 0xf6, 0x2b, 0xd8, 0xa8, 0x11, 0x1e, 0x6c, 0x65, 0x12, 0xdc, 0xcd, 0xec, 0x39, 0xa4, 0x27, - 0x66, 0xf7, 0x72, 0xf7, 0x97, 0x02, 0xf1, 0x43, 0xda, 0x41, 0x69, 0x88, 0xb9, 0x3d, 0x19, 0x77, - 0xcc, 0xed, 0xa1, 0x7c, 0xa4, 0xe5, 0xee, 0x2c, 0x9a, 0x47, 0x3a, 0x6d, 0x82, 0x71, 0x9b, 0x8f, - 0x99, 0xa8, 0xf2, 0x74, 0x51, 0x5b, 0x44, 0xb7, 0x84, 0x1e, 0x4b, 0x1c, 0xda, 0x86, 0x55, 0xe2, - 0xfb, 0xd4, 0x17, 0xad, 0x30, 0x89, 0xc3, 0x0d, 0x7a, 0x04, 0x49, 0xc7, 0x1d, 0x10, 0x6b, 0xec, - 0xbb, 0x4c, 0x5b, 0xcd, 0xc4, 0x73, 0x49, 0xbc, 0x1e, 0x08, 0x4e, 0x7c, 0x97, 0xa1, 0x17, 0x90, - 0x0a, 0x52, 0xd3, 0x72, 0xa8, 0x3f, 0xb4, 0xb9, 0x96, 0x10, 0x9e, 0xf6, 0x96, 0xe4, 0x72, 0x55, - 0x00, 0x30, 0xf4, 0xa6, 0xeb, 0xec, 0x1f, 0x0a, 0x6c, 0x44, 0xd2, 0x1c, 0x1d, 0x42, 0x4a, 0xb8, - 0x92, 0x95, 0x11, 0x92, 0x74, 0x70, 0x5b, 0x65, 0x18, 0x55, 0x77, 0x40, 0xc2, 0xe5, 0x9b, 0x07, - 0x18, 0x9c, 0xe9, 0x4e, 0x27, 0x00, 0xd7, 0xba, 0x68, 0x10, 0xca, 0xed, 0x41, 0xc4, 0xee, 0x11, - 0x44, 0x59, 0x9d, 0xaf, 0xe7, 0x3c, 0x15, 0xbf, 0xab, 0xc8, 0xbf, 0x0f, 0x3d, 0x01, 0xbd, 0x6a, - 0x96, 0x5a, 0x6d, 0xab, 0x65, 0xe2, 0xd3, 0x7a, 0xa3, 0x66, 0xb5, 0xbf, 0x3f, 0x36, 0xad, 0x7a, - 0xe3, 0xb4, 0x74, 0x54, 0xaf, 0xa8, 0x0f, 0xd0, 0x63, 0xd8, 0x5b, 0xa2, 0x6f, 0x36, 0x8e, 0xea, - 0x0d, 0x53, 0x55, 0xd0, 0x3e, 0x68, 0x4b, 0xd4, 0xe5, 0x52, 0xfb, 0xf5, 0x1b, 0x35, 0x96, 0x7f, - 0x0e, 0x6b, 0xf2, 0xe5, 0xd1, 0x36, 0xa8, 0x87, 0xcd, 0xf2, 0xfc, 0xe9, 0xff, 0x87, 0x87, 0x53, - 0x69, 0xa5, 0xf9, 0xae, 0x71, 0xd4, 0x2c, 0x55, 0x54, 0x25, 0x7f, 0x06, 0xc9, 0x69, 0x0e, 0xa0, - 0x1d, 0x40, 0x01, 0xa6, 0xd5, 0x2e, 0xb5, 0x4f, 0x5a, 0x33, 0xb6, 0x51, 0xf9, 0xb1, 0xd9, 0xa8, - 0xd4, 0x1b, 0x35, 0x55, 0x99, 0x93, 0xe3, 0x93, 0x46, 0x23, 0x90, 0xc7, 0xd0, 0x16, 0x6c, 0xce, - 0xc8, 0x2b, 0xcd, 0x86, 0xa9, 0xc6, 0xf3, 0x2f, 0x01, 0xae, 0xe9, 0x43, 0xbb, 0xb0, 0x55, 0x29, - 0xb5, 0x4b, 0x56, 0xb5, 0x89, 0xdf, 0x96, 0xda, 0x33, 0xbe, 0xb6, 0x41, 0x9d, 0x55, 0x94, 0x4e, - 0x71, 0x53, 0x55, 0x8a, 0x3f, 0xc7, 0x21, 0x1d, 0x1d, 0xa0, 0xd0, 0x00, 0xb6, 0x96, 0x8c, 0x2c, - 0xe8, 0xb3, 0xc5, 0xfe, 0x73, 0xc3, 0xd4, 0xa3, 0xe7, 0xef, 0x02, 0x95, 0x25, 0xeb, 0xc0, 0xc3, - 0x85, 0x4e, 0x86, 0x0e, 0xee, 0xf8, 0xdb, 0xd2, 0x73, 0x77, 0x6d, 0x8a, 0xa8, 0x0b, 0xea, 0x7c, - 0x97, 0x42, 0x9f, 0x2e, 0x5a, 0x2f, 0xfb, 0x27, 0xe9, 0x07, 0x1f, 0xc5, 0x49, 0x27, 0x26, 0x24, - 0xc2, 0x8e, 0x84, 0xf6, 0x17, 0x4d, 0xae, 0xfb, 0x9b, 0xfe, 0xf8, 0x06, 0x6d, 0x78, 0x4c, 0xb9, - 0x0d, 0xd1, 0xf1, 0xb5, 0xbc, 0x29, 0x99, 0x2b, 0x1d, 0xd7, 0x8f, 0x83, 0x29, 0xe0, 0x87, 0x62, - 0xdf, 0xe5, 0x67, 0xe3, 0x8e, 0xd1, 0xa5, 0xc3, 0x42, 0x9f, 0x9e, 0x93, 0x8b, 0x82, 0x9c, 0x89, - 0x7b, 0x17, 0x85, 0x3e, 0x0d, 0xa7, 0x58, 0x56, 0x88, 0xcc, 0xc9, 0x9d, 0x84, 0x90, 0x3e, 0xfb, - 0x27, 0x00, 0x00, 0xff, 0xff, 0x36, 0xb8, 0x08, 0x14, 0x3f, 0x0b, 0x00, 0x00, +var File_feast_serving_ServingService_proto protoreflect.FileDescriptor + +var file_feast_serving_ServingService_proto_rawDesc = []byte{ + 0x0a, 0x22, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2f, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x6e, 0x67, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x2f, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x1c, 0x0a, + 0x1a, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, + 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x9e, 0x01, 0x0a, 0x1b, + 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x49, + 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x33, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x6e, 0x67, 0x2e, 0x46, 0x65, 0x61, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, + 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x6a, 0x6f, + 0x62, 0x5f, 0x73, 0x74, 0x61, 0x67, 0x69, 0x6e, 0x67, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x6a, 0x6f, 0x62, 0x53, 0x74, 0x61, + 0x67, 0x69, 0x6e, 0x67, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x6d, 0x0a, 0x10, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, + 0x0a, 0x0b, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x4a, + 0x04, 0x08, 0x03, 0x10, 0x04, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x22, 0xe1, 0x03, 0x0a, 0x18, + 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x66, 0x65, 0x61, + 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x08, 0x66, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x52, 0x0a, 0x0b, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, + 0x72, 0x6f, 0x77, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x66, 0x65, 0x61, + 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x6e, + 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x6f, 0x77, 0x52, 0x0a, 0x65, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x6f, 0x77, 0x73, 0x12, 0x39, 0x0a, 0x19, 0x6f, 0x6d, 0x69, + 0x74, 0x5f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x5f, 0x69, 0x6e, 0x5f, 0x72, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x6f, 0x6d, + 0x69, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x49, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0xf8, 0x01, 0x0a, 0x09, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, + 0x6f, 0x77, 0x12, 0x45, 0x0a, 0x10, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x55, 0x0a, 0x06, 0x66, 0x69, 0x65, + 0x6c, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x66, 0x65, 0x61, 0x73, + 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x6c, + 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x6f, 0x77, 0x2e, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, + 0x1a, 0x4d, 0x0a, 0x0b, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x28, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x12, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, + 0x9b, 0x01, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x08, 0x66, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, + 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x46, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x08, + 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0e, 0x64, 0x61, 0x74, 0x61, + 0x73, 0x65, 0x74, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, + 0x2e, 0x44, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0d, + 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0xad, 0x02, + 0x0a, 0x19, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x34, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, + 0x67, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x73, 0x1a, 0xb6, 0x01, 0x0a, 0x0b, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x73, 0x12, 0x58, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x1a, 0x4d, + 0x0a, 0x0b, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x28, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, + 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x40, 0x0a, + 0x18, 0x47, 0x65, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, 0x6a, 0x6f, 0x62, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x4a, 0x6f, 0x62, 0x52, 0x03, 0x6a, 0x6f, 0x62, 0x22, + 0x35, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x24, 0x0a, 0x03, 0x6a, 0x6f, 0x62, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, + 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x4a, 0x6f, + 0x62, 0x52, 0x03, 0x6a, 0x6f, 0x62, 0x22, 0x36, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4a, 0x6f, 0x62, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, 0x6a, 0x6f, 0x62, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x4a, 0x6f, 0x62, 0x52, 0x03, 0x6a, 0x6f, 0x62, 0x22, 0xe2, + 0x01, 0x0a, 0x03, 0x4a, 0x6f, 0x62, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2a, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x4a, 0x6f, 0x62, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x12, 0x30, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x6e, 0x67, 0x2e, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, + 0x6c, 0x65, 0x5f, 0x75, 0x72, 0x69, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x66, + 0x69, 0x6c, 0x65, 0x55, 0x72, 0x69, 0x73, 0x12, 0x3a, 0x0a, 0x0b, 0x64, 0x61, 0x74, 0x61, 0x5f, + 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x66, + 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x44, 0x61, 0x74, + 0x61, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x52, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x46, 0x6f, 0x72, + 0x6d, 0x61, 0x74, 0x22, 0xd4, 0x01, 0x0a, 0x0d, 0x44, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x53, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x4a, 0x0a, 0x0b, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x66, 0x65, 0x61, + 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x73, + 0x65, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x48, 0x00, 0x52, 0x0a, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x1a, 0x65, 0x0a, 0x0a, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, + 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x75, 0x72, 0x69, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x55, 0x72, 0x69, 0x73, 0x12, 0x3a, 0x0a, 0x0b, + 0x64, 0x61, 0x74, 0x61, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x19, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, + 0x67, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x52, 0x0a, 0x64, 0x61, + 0x74, 0x61, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x42, 0x10, 0x0a, 0x0e, 0x64, 0x61, 0x74, 0x61, + 0x73, 0x65, 0x74, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2a, 0x6f, 0x0a, 0x10, 0x46, 0x65, + 0x61, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1e, + 0x0a, 0x1a, 0x46, 0x45, 0x41, 0x53, 0x54, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x5f, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x00, 0x12, 0x1d, + 0x0a, 0x19, 0x46, 0x45, 0x41, 0x53, 0x54, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x5f, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4f, 0x4e, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x01, 0x12, 0x1c, 0x0a, + 0x18, 0x46, 0x45, 0x41, 0x53, 0x54, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x5f, 0x54, + 0x59, 0x50, 0x45, 0x5f, 0x42, 0x41, 0x54, 0x43, 0x48, 0x10, 0x02, 0x2a, 0x36, 0x0a, 0x07, 0x4a, + 0x6f, 0x62, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x4a, 0x4f, 0x42, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, + 0x4a, 0x4f, 0x42, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x4f, 0x57, 0x4e, 0x4c, 0x4f, 0x41, + 0x44, 0x10, 0x01, 0x2a, 0x68, 0x0a, 0x09, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x16, 0x0a, 0x12, 0x4a, 0x4f, 0x42, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x49, + 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x4a, 0x4f, 0x42, 0x5f, + 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, + 0x12, 0x16, 0x0a, 0x12, 0x4a, 0x4f, 0x42, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x52, + 0x55, 0x4e, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x4a, 0x4f, 0x42, 0x5f, + 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x44, 0x4f, 0x4e, 0x45, 0x10, 0x03, 0x2a, 0x3b, 0x0a, + 0x0a, 0x44, 0x61, 0x74, 0x61, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x17, 0x0a, 0x13, 0x44, + 0x41, 0x54, 0x41, 0x5f, 0x46, 0x4f, 0x52, 0x4d, 0x41, 0x54, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, + 0x49, 0x44, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x46, 0x4f, 0x52, + 0x4d, 0x41, 0x54, 0x5f, 0x41, 0x56, 0x52, 0x4f, 0x10, 0x01, 0x32, 0x92, 0x03, 0x0a, 0x0e, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6c, 0x0a, + 0x13, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, + 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x29, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x73, 0x74, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x2a, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, + 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x49, + 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x66, 0x0a, 0x11, 0x47, + 0x65, 0x74, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, + 0x12, 0x27, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, + 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x66, 0x65, 0x61, 0x73, + 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x6c, + 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x63, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x46, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x26, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x27, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, + 0x47, 0x65, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x06, 0x47, 0x65, 0x74, 0x4a, + 0x6f, 0x62, 0x12, 0x1c, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x74, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1d, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, + 0x2e, 0x47, 0x65, 0x74, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, + 0x5e, 0x0a, 0x13, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x42, 0x0f, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x41, + 0x50, 0x49, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x5a, 0x36, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x66, 0x65, + 0x61, 0x73, 0x74, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x73, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_feast_serving_ServingService_proto_rawDescOnce sync.Once + file_feast_serving_ServingService_proto_rawDescData = file_feast_serving_ServingService_proto_rawDesc +) + +func file_feast_serving_ServingService_proto_rawDescGZIP() []byte { + file_feast_serving_ServingService_proto_rawDescOnce.Do(func() { + file_feast_serving_ServingService_proto_rawDescData = protoimpl.X.CompressGZIP(file_feast_serving_ServingService_proto_rawDescData) + }) + return file_feast_serving_ServingService_proto_rawDescData +} + +var file_feast_serving_ServingService_proto_enumTypes = make([]protoimpl.EnumInfo, 4) +var file_feast_serving_ServingService_proto_msgTypes = make([]protoimpl.MessageInfo, 16) +var file_feast_serving_ServingService_proto_goTypes = []interface{}{ + (FeastServingType)(0), // 0: feast.serving.FeastServingType + (JobType)(0), // 1: feast.serving.JobType + (JobStatus)(0), // 2: feast.serving.JobStatus + (DataFormat)(0), // 3: feast.serving.DataFormat + (*GetFeastServingInfoRequest)(nil), // 4: feast.serving.GetFeastServingInfoRequest + (*GetFeastServingInfoResponse)(nil), // 5: feast.serving.GetFeastServingInfoResponse + (*FeatureReference)(nil), // 6: feast.serving.FeatureReference + (*GetOnlineFeaturesRequest)(nil), // 7: feast.serving.GetOnlineFeaturesRequest + (*GetBatchFeaturesRequest)(nil), // 8: feast.serving.GetBatchFeaturesRequest + (*GetOnlineFeaturesResponse)(nil), // 9: feast.serving.GetOnlineFeaturesResponse + (*GetBatchFeaturesResponse)(nil), // 10: feast.serving.GetBatchFeaturesResponse + (*GetJobRequest)(nil), // 11: feast.serving.GetJobRequest + (*GetJobResponse)(nil), // 12: feast.serving.GetJobResponse + (*Job)(nil), // 13: feast.serving.Job + (*DatasetSource)(nil), // 14: feast.serving.DatasetSource + (*GetOnlineFeaturesRequest_EntityRow)(nil), // 15: feast.serving.GetOnlineFeaturesRequest.EntityRow + nil, // 16: feast.serving.GetOnlineFeaturesRequest.EntityRow.FieldsEntry + (*GetOnlineFeaturesResponse_FieldValues)(nil), // 17: feast.serving.GetOnlineFeaturesResponse.FieldValues + nil, // 18: feast.serving.GetOnlineFeaturesResponse.FieldValues.FieldsEntry + (*DatasetSource_FileSource)(nil), // 19: feast.serving.DatasetSource.FileSource + (*timestamp.Timestamp)(nil), // 20: google.protobuf.Timestamp + (*types.Value)(nil), // 21: feast.types.Value +} +var file_feast_serving_ServingService_proto_depIdxs = []int32{ + 0, // 0: feast.serving.GetFeastServingInfoResponse.type:type_name -> feast.serving.FeastServingType + 6, // 1: feast.serving.GetOnlineFeaturesRequest.features:type_name -> feast.serving.FeatureReference + 15, // 2: feast.serving.GetOnlineFeaturesRequest.entity_rows:type_name -> feast.serving.GetOnlineFeaturesRequest.EntityRow + 6, // 3: feast.serving.GetBatchFeaturesRequest.features:type_name -> feast.serving.FeatureReference + 14, // 4: feast.serving.GetBatchFeaturesRequest.dataset_source:type_name -> feast.serving.DatasetSource + 17, // 5: feast.serving.GetOnlineFeaturesResponse.field_values:type_name -> feast.serving.GetOnlineFeaturesResponse.FieldValues + 13, // 6: feast.serving.GetBatchFeaturesResponse.job:type_name -> feast.serving.Job + 13, // 7: feast.serving.GetJobRequest.job:type_name -> feast.serving.Job + 13, // 8: feast.serving.GetJobResponse.job:type_name -> feast.serving.Job + 1, // 9: feast.serving.Job.type:type_name -> feast.serving.JobType + 2, // 10: feast.serving.Job.status:type_name -> feast.serving.JobStatus + 3, // 11: feast.serving.Job.data_format:type_name -> feast.serving.DataFormat + 19, // 12: feast.serving.DatasetSource.file_source:type_name -> feast.serving.DatasetSource.FileSource + 20, // 13: feast.serving.GetOnlineFeaturesRequest.EntityRow.entity_timestamp:type_name -> google.protobuf.Timestamp + 16, // 14: feast.serving.GetOnlineFeaturesRequest.EntityRow.fields:type_name -> feast.serving.GetOnlineFeaturesRequest.EntityRow.FieldsEntry + 21, // 15: feast.serving.GetOnlineFeaturesRequest.EntityRow.FieldsEntry.value:type_name -> feast.types.Value + 18, // 16: feast.serving.GetOnlineFeaturesResponse.FieldValues.fields:type_name -> feast.serving.GetOnlineFeaturesResponse.FieldValues.FieldsEntry + 21, // 17: feast.serving.GetOnlineFeaturesResponse.FieldValues.FieldsEntry.value:type_name -> feast.types.Value + 3, // 18: feast.serving.DatasetSource.FileSource.data_format:type_name -> feast.serving.DataFormat + 4, // 19: feast.serving.ServingService.GetFeastServingInfo:input_type -> feast.serving.GetFeastServingInfoRequest + 7, // 20: feast.serving.ServingService.GetOnlineFeatures:input_type -> feast.serving.GetOnlineFeaturesRequest + 8, // 21: feast.serving.ServingService.GetBatchFeatures:input_type -> feast.serving.GetBatchFeaturesRequest + 11, // 22: feast.serving.ServingService.GetJob:input_type -> feast.serving.GetJobRequest + 5, // 23: feast.serving.ServingService.GetFeastServingInfo:output_type -> feast.serving.GetFeastServingInfoResponse + 9, // 24: feast.serving.ServingService.GetOnlineFeatures:output_type -> feast.serving.GetOnlineFeaturesResponse + 10, // 25: feast.serving.ServingService.GetBatchFeatures:output_type -> feast.serving.GetBatchFeaturesResponse + 12, // 26: feast.serving.ServingService.GetJob:output_type -> feast.serving.GetJobResponse + 23, // [23:27] is the sub-list for method output_type + 19, // [19:23] is the sub-list for method input_type + 19, // [19:19] is the sub-list for extension type_name + 19, // [19:19] is the sub-list for extension extendee + 0, // [0:19] is the sub-list for field type_name +} + +func init() { file_feast_serving_ServingService_proto_init() } +func file_feast_serving_ServingService_proto_init() { + if File_feast_serving_ServingService_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_feast_serving_ServingService_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetFeastServingInfoRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_serving_ServingService_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetFeastServingInfoResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_serving_ServingService_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FeatureReference); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_serving_ServingService_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetOnlineFeaturesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_serving_ServingService_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBatchFeaturesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_serving_ServingService_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetOnlineFeaturesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_serving_ServingService_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBatchFeaturesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_serving_ServingService_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetJobRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_serving_ServingService_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetJobResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_serving_ServingService_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Job); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_serving_ServingService_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DatasetSource); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_serving_ServingService_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetOnlineFeaturesRequest_EntityRow); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_serving_ServingService_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetOnlineFeaturesResponse_FieldValues); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_serving_ServingService_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DatasetSource_FileSource); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_feast_serving_ServingService_proto_msgTypes[10].OneofWrappers = []interface{}{ + (*DatasetSource_FileSource_)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_feast_serving_ServingService_proto_rawDesc, + NumEnums: 4, + NumMessages: 16, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_feast_serving_ServingService_proto_goTypes, + DependencyIndexes: file_feast_serving_ServingService_proto_depIdxs, + EnumInfos: file_feast_serving_ServingService_proto_enumTypes, + MessageInfos: file_feast_serving_ServingService_proto_msgTypes, + }.Build() + File_feast_serving_ServingService_proto = out.File + file_feast_serving_ServingService_proto_rawDesc = nil + file_feast_serving_ServingService_proto_goTypes = nil + file_feast_serving_ServingService_proto_depIdxs = nil } // Reference imports to suppress errors if they are not otherwise used. var _ context.Context -var _ grpc.ClientConn +var _ grpc.ClientConnInterface // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion4 +const _ = grpc.SupportPackageIsVersion6 // ServingServiceClient is the client API for ServingService service. // @@ -991,10 +1544,10 @@ type ServingServiceClient interface { } type servingServiceClient struct { - cc *grpc.ClientConn + cc grpc.ClientConnInterface } -func NewServingServiceClient(cc *grpc.ClientConn) ServingServiceClient { +func NewServingServiceClient(cc grpc.ClientConnInterface) ServingServiceClient { return &servingServiceClient{cc} } @@ -1057,16 +1610,16 @@ type ServingServiceServer interface { type UnimplementedServingServiceServer struct { } -func (*UnimplementedServingServiceServer) GetFeastServingInfo(ctx context.Context, req *GetFeastServingInfoRequest) (*GetFeastServingInfoResponse, error) { +func (*UnimplementedServingServiceServer) GetFeastServingInfo(context.Context, *GetFeastServingInfoRequest) (*GetFeastServingInfoResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetFeastServingInfo not implemented") } -func (*UnimplementedServingServiceServer) GetOnlineFeatures(ctx context.Context, req *GetOnlineFeaturesRequest) (*GetOnlineFeaturesResponse, error) { +func (*UnimplementedServingServiceServer) GetOnlineFeatures(context.Context, *GetOnlineFeaturesRequest) (*GetOnlineFeaturesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetOnlineFeatures not implemented") } -func (*UnimplementedServingServiceServer) GetBatchFeatures(ctx context.Context, req *GetBatchFeaturesRequest) (*GetBatchFeaturesResponse, error) { +func (*UnimplementedServingServiceServer) GetBatchFeatures(context.Context, *GetBatchFeaturesRequest) (*GetBatchFeaturesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetBatchFeatures not implemented") } -func (*UnimplementedServingServiceServer) GetJob(ctx context.Context, req *GetJobRequest) (*GetJobResponse, error) { +func (*UnimplementedServingServiceServer) GetJob(context.Context, *GetJobRequest) (*GetJobResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetJob not implemented") } diff --git a/sdk/go/protos/feast/storage/Redis.pb.go b/sdk/go/protos/feast/storage/Redis.pb.go index 55cf42becd8..cfc22a458e5 100644 --- a/sdk/go/protos/feast/storage/Redis.pb.go +++ b/sdk/go/protos/feast/storage/Redis.pb.go @@ -1,95 +1,188 @@ +// +// Copyright 2019 The Feast Authors +// +// 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.23.0 +// protoc v3.10.0 // source: feast/storage/Redis.proto package storage import ( - fmt "fmt" - types "github.com/gojek/feast/sdk/go/protos/feast/types" + types "github.com/feast-dev/feast/sdk/go/protos/feast/types" proto "github.com/golang/protobuf/proto" - math "math" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" ) -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf +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 proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 type RedisKey struct { - // FeatureSet this row belongs to, this is defined as featureSetName:version. + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // FeatureSet this row belongs to, this is defined as featureSetName. FeatureSet string `protobuf:"bytes,2,opt,name=feature_set,json=featureSet,proto3" json:"feature_set,omitempty"` // List of fields containing entity names and their respective values - // contained within this feature row. - Entities []*types.Field `protobuf:"bytes,3,rep,name=entities,proto3" json:"entities,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + // contained within this feature row. The entities should be sorted + // by the entity name alphabetically in ascending order. + Entities []*types.Field `protobuf:"bytes,3,rep,name=entities,proto3" json:"entities,omitempty"` } -func (m *RedisKey) Reset() { *m = RedisKey{} } -func (m *RedisKey) String() string { return proto.CompactTextString(m) } -func (*RedisKey) ProtoMessage() {} -func (*RedisKey) Descriptor() ([]byte, []int) { - return fileDescriptor_64e898a359fc9e5d, []int{0} +func (x *RedisKey) Reset() { + *x = RedisKey{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_storage_Redis_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *RedisKey) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_RedisKey.Unmarshal(m, b) -} -func (m *RedisKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_RedisKey.Marshal(b, m, deterministic) +func (x *RedisKey) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *RedisKey) XXX_Merge(src proto.Message) { - xxx_messageInfo_RedisKey.Merge(m, src) -} -func (m *RedisKey) XXX_Size() int { - return xxx_messageInfo_RedisKey.Size(m) -} -func (m *RedisKey) XXX_DiscardUnknown() { - xxx_messageInfo_RedisKey.DiscardUnknown(m) + +func (*RedisKey) ProtoMessage() {} + +func (x *RedisKey) ProtoReflect() protoreflect.Message { + mi := &file_feast_storage_Redis_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_RedisKey proto.InternalMessageInfo +// Deprecated: Use RedisKey.ProtoReflect.Descriptor instead. +func (*RedisKey) Descriptor() ([]byte, []int) { + return file_feast_storage_Redis_proto_rawDescGZIP(), []int{0} +} -func (m *RedisKey) GetFeatureSet() string { - if m != nil { - return m.FeatureSet +func (x *RedisKey) GetFeatureSet() string { + if x != nil { + return x.FeatureSet } return "" } -func (m *RedisKey) GetEntities() []*types.Field { - if m != nil { - return m.Entities +func (x *RedisKey) GetEntities() []*types.Field { + if x != nil { + return x.Entities } return nil } -func init() { - proto.RegisterType((*RedisKey)(nil), "feast.storage.RedisKey") +var File_feast_storage_Redis_proto protoreflect.FileDescriptor + +var file_feast_storage_Redis_proto_rawDesc = []byte{ + 0x0a, 0x19, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, + 0x52, 0x65, 0x64, 0x69, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x66, 0x65, 0x61, + 0x73, 0x74, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x1a, 0x17, 0x66, 0x65, 0x61, 0x73, + 0x74, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x22, 0x5b, 0x0a, 0x08, 0x52, 0x65, 0x64, 0x69, 0x73, 0x4b, 0x65, 0x79, 0x12, + 0x1f, 0x0a, 0x0b, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, + 0x12, 0x2e, 0x0a, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, + 0x42, 0x59, 0x0a, 0x13, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x42, 0x0a, 0x52, 0x65, 0x64, 0x69, 0x73, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x5a, 0x36, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x66, 0x65, 0x61, 0x73, 0x74, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, + 0x73, 0x64, 0x6b, 0x2f, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x66, 0x65, + 0x61, 0x73, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, +} + +var ( + file_feast_storage_Redis_proto_rawDescOnce sync.Once + file_feast_storage_Redis_proto_rawDescData = file_feast_storage_Redis_proto_rawDesc +) + +func file_feast_storage_Redis_proto_rawDescGZIP() []byte { + file_feast_storage_Redis_proto_rawDescOnce.Do(func() { + file_feast_storage_Redis_proto_rawDescData = protoimpl.X.CompressGZIP(file_feast_storage_Redis_proto_rawDescData) + }) + return file_feast_storage_Redis_proto_rawDescData } -func init() { proto.RegisterFile("feast/storage/Redis.proto", fileDescriptor_64e898a359fc9e5d) } - -var fileDescriptor_64e898a359fc9e5d = []byte{ - // 193 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0x8f, 0x31, 0xaf, 0x82, 0x40, - 0x10, 0x84, 0xf3, 0x1e, 0xc9, 0x0b, 0xef, 0x88, 0xcd, 0x35, 0xa2, 0x8d, 0xc4, 0x8a, 0xea, 0x36, - 0xc1, 0x7f, 0x40, 0x61, 0x63, 0xa1, 0xc1, 0x4e, 0x0b, 0x03, 0xb2, 0x9c, 0x27, 0xea, 0x11, 0x6e, - 0x29, 0xf8, 0xf7, 0xc6, 0x85, 0x98, 0xd0, 0xee, 0xcc, 0xce, 0x37, 0x23, 0x16, 0x15, 0xe6, 0x8e, - 0xc0, 0x91, 0x6d, 0x73, 0x8d, 0x90, 0x61, 0x69, 0x9c, 0x6a, 0x5a, 0x4b, 0x56, 0xce, 0x58, 0x52, - 0xa3, 0xb4, 0x9c, 0x0f, 0x4e, 0xea, 0x1b, 0x74, 0xb0, 0x35, 0xf8, 0x28, 0x07, 0xdf, 0xfa, 0x2c, - 0x7c, 0x7e, 0xdb, 0x61, 0x2f, 0x57, 0x22, 0xa8, 0x30, 0xa7, 0xae, 0xc5, 0x8b, 0x43, 0x0a, 0x7f, - 0xa3, 0x9f, 0xf8, 0x3f, 0x13, 0xe3, 0xe9, 0x88, 0x24, 0x95, 0xf0, 0xf1, 0x45, 0x86, 0x0c, 0xba, - 0xd0, 0x8b, 0xbc, 0x38, 0x48, 0xa4, 0x1a, 0x38, 0x1c, 0xac, 0x38, 0x38, 0xfb, 0x7a, 0xd2, 0xbd, - 0x98, 0xd6, 0x48, 0x05, 0xb3, 0x0e, 0x1f, 0xf2, 0x29, 0xd1, 0x86, 0x6e, 0x5d, 0xa1, 0xae, 0xf6, - 0x09, 0xda, 0xde, 0xb1, 0x86, 0x71, 0x4d, 0x59, 0x83, 0xb6, 0xc0, 0xf5, 0x1c, 0x4c, 0x16, 0x16, - 0x7f, 0x7c, 0xdd, 0xbc, 0x03, 0x00, 0x00, 0xff, 0xff, 0x68, 0x2b, 0x58, 0x50, 0xf9, 0x00, 0x00, - 0x00, +var file_feast_storage_Redis_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_feast_storage_Redis_proto_goTypes = []interface{}{ + (*RedisKey)(nil), // 0: feast.storage.RedisKey + (*types.Field)(nil), // 1: feast.types.Field +} +var file_feast_storage_Redis_proto_depIdxs = []int32{ + 1, // 0: feast.storage.RedisKey.entities:type_name -> feast.types.Field + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_feast_storage_Redis_proto_init() } +func file_feast_storage_Redis_proto_init() { + if File_feast_storage_Redis_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_feast_storage_Redis_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RedisKey); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_feast_storage_Redis_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_feast_storage_Redis_proto_goTypes, + DependencyIndexes: file_feast_storage_Redis_proto_depIdxs, + MessageInfos: file_feast_storage_Redis_proto_msgTypes, + }.Build() + File_feast_storage_Redis_proto = out.File + file_feast_storage_Redis_proto_rawDesc = nil + file_feast_storage_Redis_proto_goTypes = nil + file_feast_storage_Redis_proto_depIdxs = nil } diff --git a/sdk/go/protos/feast/types/FeatureRow.pb.go b/sdk/go/protos/feast/types/FeatureRow.pb.go index dd9aa6b8c96..3b42b44889e 100644 --- a/sdk/go/protos/feast/types/FeatureRow.pb.go +++ b/sdk/go/protos/feast/types/FeatureRow.pb.go @@ -1,27 +1,51 @@ +// +// Copyright 2018 The Feast Authors +// +// 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.23.0 +// protoc v3.10.0 // source: feast/types/FeatureRow.proto package types import ( - fmt "fmt" proto "github.com/golang/protobuf/proto" timestamp "github.com/golang/protobuf/ptypes/timestamp" - math "math" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" ) -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf +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 proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 type FeatureRow struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // Fields in the feature row. Fields []*Field `protobuf:"bytes,2,rep,name=fields,proto3" json:"fields,omitempty"` // Timestamp of the feature row. While the actual definition of this timestamp may vary @@ -29,81 +53,167 @@ type FeatureRow struct { // will use to perform joins, determine latest values, and coalesce rows. EventTimestamp *timestamp.Timestamp `protobuf:"bytes,3,opt,name=event_timestamp,json=eventTimestamp,proto3" json:"event_timestamp,omitempty"` // Complete reference to the featureSet this featureRow belongs to, in the form of - // featureSetName:version. This value will be used by the feast ingestion job to filter + // /. This value will be used by the feast ingestion job to filter // rows, and write the values to the correct tables. - FeatureSet string `protobuf:"bytes,6,opt,name=feature_set,json=featureSet,proto3" json:"feature_set,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + FeatureSet string `protobuf:"bytes,6,opt,name=feature_set,json=featureSet,proto3" json:"feature_set,omitempty"` + // Identifier tying this feature row to a specific ingestion job. + IngestionId string `protobuf:"bytes,7,opt,name=ingestion_id,json=ingestionId,proto3" json:"ingestion_id,omitempty"` } -func (m *FeatureRow) Reset() { *m = FeatureRow{} } -func (m *FeatureRow) String() string { return proto.CompactTextString(m) } -func (*FeatureRow) ProtoMessage() {} -func (*FeatureRow) Descriptor() ([]byte, []int) { - return fileDescriptor_fbbea9c89787d1c7, []int{0} +func (x *FeatureRow) Reset() { + *x = FeatureRow{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_types_FeatureRow_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *FeatureRow) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_FeatureRow.Unmarshal(m, b) -} -func (m *FeatureRow) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_FeatureRow.Marshal(b, m, deterministic) -} -func (m *FeatureRow) XXX_Merge(src proto.Message) { - xxx_messageInfo_FeatureRow.Merge(m, src) +func (x *FeatureRow) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *FeatureRow) XXX_Size() int { - return xxx_messageInfo_FeatureRow.Size(m) -} -func (m *FeatureRow) XXX_DiscardUnknown() { - xxx_messageInfo_FeatureRow.DiscardUnknown(m) + +func (*FeatureRow) ProtoMessage() {} + +func (x *FeatureRow) ProtoReflect() protoreflect.Message { + mi := &file_feast_types_FeatureRow_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_FeatureRow proto.InternalMessageInfo +// Deprecated: Use FeatureRow.ProtoReflect.Descriptor instead. +func (*FeatureRow) Descriptor() ([]byte, []int) { + return file_feast_types_FeatureRow_proto_rawDescGZIP(), []int{0} +} -func (m *FeatureRow) GetFields() []*Field { - if m != nil { - return m.Fields +func (x *FeatureRow) GetFields() []*Field { + if x != nil { + return x.Fields } return nil } -func (m *FeatureRow) GetEventTimestamp() *timestamp.Timestamp { - if m != nil { - return m.EventTimestamp +func (x *FeatureRow) GetEventTimestamp() *timestamp.Timestamp { + if x != nil { + return x.EventTimestamp } return nil } -func (m *FeatureRow) GetFeatureSet() string { - if m != nil { - return m.FeatureSet +func (x *FeatureRow) GetFeatureSet() string { + if x != nil { + return x.FeatureSet } return "" } -func init() { - proto.RegisterType((*FeatureRow)(nil), "feast.types.FeatureRow") +func (x *FeatureRow) GetIngestionId() string { + if x != nil { + return x.IngestionId + } + return "" +} + +var File_feast_types_FeatureRow_proto protoreflect.FileDescriptor + +var file_feast_types_FeatureRow_proto_rawDesc = []byte{ + 0x0a, 0x1c, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x46, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x6f, 0x77, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, + 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x66, 0x65, + 0x61, 0x73, 0x74, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc1, 0x01, 0x0a, 0x0a, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x52, 0x6f, 0x77, 0x12, 0x2a, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x74, 0x79, 0x70, + 0x65, 0x73, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, + 0x12, 0x43, 0x0a, 0x0f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x5f, 0x73, 0x65, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x66, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x6e, + 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x42, 0x5a, 0x0a, 0x11, 0x66, 0x65, 0x61, + 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x42, 0x0f, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x6f, 0x77, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x5a, + 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x66, 0x65, 0x61, 0x73, + 0x74, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x73, 0x64, 0x6b, 0x2f, + 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } -func init() { proto.RegisterFile("feast/types/FeatureRow.proto", fileDescriptor_fbbea9c89787d1c7) } - -var fileDescriptor_fbbea9c89787d1c7 = []byte{ - // 238 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0x90, 0xcd, 0x4a, 0xc3, 0x40, - 0x10, 0xc7, 0x89, 0x85, 0x80, 0x1b, 0xb0, 0xb0, 0x17, 0x43, 0x10, 0x1a, 0x3c, 0x05, 0x0f, 0x33, - 0x52, 0xdf, 0xa0, 0x82, 0xe7, 0x12, 0x3d, 0x79, 0x29, 0x89, 0x9d, 0xac, 0xb1, 0x8d, 0x13, 0xba, - 0x13, 0xc5, 0x97, 0xf1, 0x59, 0x65, 0x77, 0xdb, 0x26, 0x1e, 0x77, 0x7e, 0x33, 0xff, 0x8f, 0x55, - 0x37, 0x0d, 0x55, 0x56, 0x50, 0x7e, 0x7a, 0xb2, 0xf8, 0x44, 0x95, 0x0c, 0x07, 0x2a, 0xf9, 0x1b, - 0xfa, 0x03, 0x0b, 0xeb, 0xc4, 0x53, 0xf0, 0x34, 0x5b, 0x18, 0x66, 0xb3, 0x27, 0xf4, 0xa8, 0x1e, - 0x1a, 0x94, 0xb6, 0x23, 0x2b, 0x55, 0xd7, 0x87, 0xed, 0xec, 0xfa, 0x9f, 0x56, 0x4b, 0xfb, 0x6d, - 0x00, 0xb7, 0xbf, 0x91, 0x52, 0xa3, 0xb6, 0xbe, 0x53, 0x71, 0xe3, 0xa8, 0x4d, 0x2f, 0xf2, 0x59, - 0x91, 0x2c, 0x35, 0x4c, 0x6c, 0xc0, 0x1f, 0x96, 0xc7, 0x0d, 0xfd, 0xa8, 0xe6, 0xf4, 0x45, 0x9f, - 0xb2, 0x39, 0x9b, 0xa5, 0xb3, 0x3c, 0x2a, 0x92, 0x65, 0x06, 0x21, 0x0e, 0x9c, 0xe2, 0xc0, 0xcb, - 0x69, 0xa3, 0xbc, 0xf2, 0x27, 0xe7, 0xb7, 0x5e, 0x28, 0x57, 0xc4, 0xd9, 0x6f, 0x2c, 0x49, 0x1a, - 0xe7, 0x51, 0x71, 0x59, 0xaa, 0xe3, 0xe8, 0x99, 0x64, 0xb5, 0x56, 0xd3, 0xa6, 0xab, 0xf9, 0x18, - 0x76, 0xed, 0xd4, 0x5f, 0xef, 0x4d, 0x2b, 0xef, 0x43, 0x0d, 0x6f, 0xdc, 0xa1, 0xe1, 0x0f, 0xda, - 0x61, 0xa8, 0x6a, 0xb7, 0x3b, 0x34, 0x1c, 0x7e, 0xc4, 0xe2, 0xa4, 0x7e, 0x1d, 0xfb, 0xd9, 0xc3, - 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x2f, 0x88, 0x1b, 0x19, 0x60, 0x01, 0x00, 0x00, +var ( + file_feast_types_FeatureRow_proto_rawDescOnce sync.Once + file_feast_types_FeatureRow_proto_rawDescData = file_feast_types_FeatureRow_proto_rawDesc +) + +func file_feast_types_FeatureRow_proto_rawDescGZIP() []byte { + file_feast_types_FeatureRow_proto_rawDescOnce.Do(func() { + file_feast_types_FeatureRow_proto_rawDescData = protoimpl.X.CompressGZIP(file_feast_types_FeatureRow_proto_rawDescData) + }) + return file_feast_types_FeatureRow_proto_rawDescData +} + +var file_feast_types_FeatureRow_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_feast_types_FeatureRow_proto_goTypes = []interface{}{ + (*FeatureRow)(nil), // 0: feast.types.FeatureRow + (*Field)(nil), // 1: feast.types.Field + (*timestamp.Timestamp)(nil), // 2: google.protobuf.Timestamp +} +var file_feast_types_FeatureRow_proto_depIdxs = []int32{ + 1, // 0: feast.types.FeatureRow.fields:type_name -> feast.types.Field + 2, // 1: feast.types.FeatureRow.event_timestamp:type_name -> google.protobuf.Timestamp + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_feast_types_FeatureRow_proto_init() } +func file_feast_types_FeatureRow_proto_init() { + if File_feast_types_FeatureRow_proto != nil { + return + } + file_feast_types_Field_proto_init() + if !protoimpl.UnsafeEnabled { + file_feast_types_FeatureRow_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FeatureRow); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_feast_types_FeatureRow_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_feast_types_FeatureRow_proto_goTypes, + DependencyIndexes: file_feast_types_FeatureRow_proto_depIdxs, + MessageInfos: file_feast_types_FeatureRow_proto_msgTypes, + }.Build() + File_feast_types_FeatureRow_proto = out.File + file_feast_types_FeatureRow_proto_rawDesc = nil + file_feast_types_FeatureRow_proto_goTypes = nil + file_feast_types_FeatureRow_proto_depIdxs = nil } diff --git a/sdk/go/protos/feast/types/FeatureRowExtended.pb.go b/sdk/go/protos/feast/types/FeatureRowExtended.pb.go index 734c98687a0..1692a55dd4f 100644 --- a/sdk/go/protos/feast/types/FeatureRowExtended.pb.go +++ b/sdk/go/protos/feast/types/FeatureRowExtended.pb.go @@ -1,223 +1,370 @@ +// +// Copyright 2018 The Feast Authors +// +// 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.23.0 +// protoc v3.10.0 // source: feast/types/FeatureRowExtended.proto package types import ( - fmt "fmt" proto "github.com/golang/protobuf/proto" timestamp "github.com/golang/protobuf/ptypes/timestamp" - math "math" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" ) -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf +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 proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 type Error struct { - Cause string `protobuf:"bytes,1,opt,name=cause,proto3" json:"cause,omitempty"` - Transform string `protobuf:"bytes,2,opt,name=transform,proto3" json:"transform,omitempty"` - Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"` - StackTrace string `protobuf:"bytes,4,opt,name=stack_trace,json=stackTrace,proto3" json:"stack_trace,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Error) Reset() { *m = Error{} } -func (m *Error) String() string { return proto.CompactTextString(m) } -func (*Error) ProtoMessage() {} -func (*Error) Descriptor() ([]byte, []int) { - return fileDescriptor_7823aa2c72575793, []int{0} -} + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields -func (m *Error) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Error.Unmarshal(m, b) -} -func (m *Error) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Error.Marshal(b, m, deterministic) + Cause string `protobuf:"bytes,1,opt,name=cause,proto3" json:"cause,omitempty"` // exception class name + Transform string `protobuf:"bytes,2,opt,name=transform,proto3" json:"transform,omitempty"` // name of transform where the error occurred + Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"` + StackTrace string `protobuf:"bytes,4,opt,name=stack_trace,json=stackTrace,proto3" json:"stack_trace,omitempty"` } -func (m *Error) XXX_Merge(src proto.Message) { - xxx_messageInfo_Error.Merge(m, src) + +func (x *Error) Reset() { + *x = Error{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_types_FeatureRowExtended_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *Error) XXX_Size() int { - return xxx_messageInfo_Error.Size(m) + +func (x *Error) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *Error) XXX_DiscardUnknown() { - xxx_messageInfo_Error.DiscardUnknown(m) + +func (*Error) ProtoMessage() {} + +func (x *Error) ProtoReflect() protoreflect.Message { + mi := &file_feast_types_FeatureRowExtended_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_Error proto.InternalMessageInfo +// Deprecated: Use Error.ProtoReflect.Descriptor instead. +func (*Error) Descriptor() ([]byte, []int) { + return file_feast_types_FeatureRowExtended_proto_rawDescGZIP(), []int{0} +} -func (m *Error) GetCause() string { - if m != nil { - return m.Cause +func (x *Error) GetCause() string { + if x != nil { + return x.Cause } return "" } -func (m *Error) GetTransform() string { - if m != nil { - return m.Transform +func (x *Error) GetTransform() string { + if x != nil { + return x.Transform } return "" } -func (m *Error) GetMessage() string { - if m != nil { - return m.Message +func (x *Error) GetMessage() string { + if x != nil { + return x.Message } return "" } -func (m *Error) GetStackTrace() string { - if m != nil { - return m.StackTrace +func (x *Error) GetStackTrace() string { + if x != nil { + return x.StackTrace } return "" } type Attempt struct { - Attempts int32 `protobuf:"varint,1,opt,name=attempts,proto3" json:"attempts,omitempty"` - Error *Error `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields -func (m *Attempt) Reset() { *m = Attempt{} } -func (m *Attempt) String() string { return proto.CompactTextString(m) } -func (*Attempt) ProtoMessage() {} -func (*Attempt) Descriptor() ([]byte, []int) { - return fileDescriptor_7823aa2c72575793, []int{1} + Attempts int32 `protobuf:"varint,1,opt,name=attempts,proto3" json:"attempts,omitempty"` + Error *Error `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` } -func (m *Attempt) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Attempt.Unmarshal(m, b) -} -func (m *Attempt) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Attempt.Marshal(b, m, deterministic) -} -func (m *Attempt) XXX_Merge(src proto.Message) { - xxx_messageInfo_Attempt.Merge(m, src) +func (x *Attempt) Reset() { + *x = Attempt{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_types_FeatureRowExtended_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *Attempt) XXX_Size() int { - return xxx_messageInfo_Attempt.Size(m) + +func (x *Attempt) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *Attempt) XXX_DiscardUnknown() { - xxx_messageInfo_Attempt.DiscardUnknown(m) + +func (*Attempt) ProtoMessage() {} + +func (x *Attempt) ProtoReflect() protoreflect.Message { + mi := &file_feast_types_FeatureRowExtended_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_Attempt proto.InternalMessageInfo +// Deprecated: Use Attempt.ProtoReflect.Descriptor instead. +func (*Attempt) Descriptor() ([]byte, []int) { + return file_feast_types_FeatureRowExtended_proto_rawDescGZIP(), []int{1} +} -func (m *Attempt) GetAttempts() int32 { - if m != nil { - return m.Attempts +func (x *Attempt) GetAttempts() int32 { + if x != nil { + return x.Attempts } return 0 } -func (m *Attempt) GetError() *Error { - if m != nil { - return m.Error +func (x *Attempt) GetError() *Error { + if x != nil { + return x.Error } return nil } type FeatureRowExtended struct { - Row *FeatureRow `protobuf:"bytes,1,opt,name=row,proto3" json:"row,omitempty"` - LastAttempt *Attempt `protobuf:"bytes,2,opt,name=last_attempt,json=lastAttempt,proto3" json:"last_attempt,omitempty"` - FirstSeen *timestamp.Timestamp `protobuf:"bytes,3,opt,name=first_seen,json=firstSeen,proto3" json:"first_seen,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields -func (m *FeatureRowExtended) Reset() { *m = FeatureRowExtended{} } -func (m *FeatureRowExtended) String() string { return proto.CompactTextString(m) } -func (*FeatureRowExtended) ProtoMessage() {} -func (*FeatureRowExtended) Descriptor() ([]byte, []int) { - return fileDescriptor_7823aa2c72575793, []int{2} + Row *FeatureRow `protobuf:"bytes,1,opt,name=row,proto3" json:"row,omitempty"` + LastAttempt *Attempt `protobuf:"bytes,2,opt,name=last_attempt,json=lastAttempt,proto3" json:"last_attempt,omitempty"` + FirstSeen *timestamp.Timestamp `protobuf:"bytes,3,opt,name=first_seen,json=firstSeen,proto3" json:"first_seen,omitempty"` } -func (m *FeatureRowExtended) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_FeatureRowExtended.Unmarshal(m, b) -} -func (m *FeatureRowExtended) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_FeatureRowExtended.Marshal(b, m, deterministic) -} -func (m *FeatureRowExtended) XXX_Merge(src proto.Message) { - xxx_messageInfo_FeatureRowExtended.Merge(m, src) +func (x *FeatureRowExtended) Reset() { + *x = FeatureRowExtended{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_types_FeatureRowExtended_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *FeatureRowExtended) XXX_Size() int { - return xxx_messageInfo_FeatureRowExtended.Size(m) + +func (x *FeatureRowExtended) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *FeatureRowExtended) XXX_DiscardUnknown() { - xxx_messageInfo_FeatureRowExtended.DiscardUnknown(m) + +func (*FeatureRowExtended) ProtoMessage() {} + +func (x *FeatureRowExtended) ProtoReflect() protoreflect.Message { + mi := &file_feast_types_FeatureRowExtended_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_FeatureRowExtended proto.InternalMessageInfo +// Deprecated: Use FeatureRowExtended.ProtoReflect.Descriptor instead. +func (*FeatureRowExtended) Descriptor() ([]byte, []int) { + return file_feast_types_FeatureRowExtended_proto_rawDescGZIP(), []int{2} +} -func (m *FeatureRowExtended) GetRow() *FeatureRow { - if m != nil { - return m.Row +func (x *FeatureRowExtended) GetRow() *FeatureRow { + if x != nil { + return x.Row } return nil } -func (m *FeatureRowExtended) GetLastAttempt() *Attempt { - if m != nil { - return m.LastAttempt +func (x *FeatureRowExtended) GetLastAttempt() *Attempt { + if x != nil { + return x.LastAttempt } return nil } -func (m *FeatureRowExtended) GetFirstSeen() *timestamp.Timestamp { - if m != nil { - return m.FirstSeen +func (x *FeatureRowExtended) GetFirstSeen() *timestamp.Timestamp { + if x != nil { + return x.FirstSeen } return nil } -func init() { - proto.RegisterType((*Error)(nil), "feast.types.Error") - proto.RegisterType((*Attempt)(nil), "feast.types.Attempt") - proto.RegisterType((*FeatureRowExtended)(nil), "feast.types.FeatureRowExtended") -} - -func init() { - proto.RegisterFile("feast/types/FeatureRowExtended.proto", fileDescriptor_7823aa2c72575793) -} - -var fileDescriptor_7823aa2c72575793 = []byte{ - // 345 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x91, 0xcf, 0x4e, 0xc2, 0x40, - 0x10, 0xc6, 0x83, 0x58, 0x91, 0xa9, 0xa7, 0x0d, 0x09, 0x4d, 0x43, 0x82, 0x21, 0x1e, 0xf0, 0xb2, - 0x6b, 0xf0, 0x60, 0x3c, 0x4a, 0x82, 0x57, 0x4d, 0xe5, 0x60, 0xbc, 0x90, 0xa5, 0x4c, 0x2b, 0x42, - 0xbb, 0xcd, 0xee, 0x54, 0xf4, 0xb9, 0x7c, 0x41, 0xd3, 0x5d, 0x2a, 0x10, 0xbc, 0xed, 0xcc, 0xf7, - 0x9b, 0x9d, 0x3f, 0x1f, 0x5c, 0x25, 0x28, 0x0d, 0x09, 0xfa, 0x2e, 0xd0, 0x88, 0x47, 0x94, 0x54, - 0x6a, 0x8c, 0xd4, 0x66, 0xf2, 0x45, 0x98, 0x2f, 0x70, 0xc1, 0x0b, 0xad, 0x48, 0x31, 0xdf, 0x52, - 0xdc, 0x52, 0x61, 0x3f, 0x55, 0x2a, 0x5d, 0xa3, 0xb0, 0xd2, 0xbc, 0x4c, 0x04, 0x2d, 0x33, 0x34, - 0x24, 0xb3, 0xc2, 0xd1, 0x61, 0xef, 0xff, 0x3f, 0x9d, 0x3a, 0xf8, 0x04, 0x6f, 0xa2, 0xb5, 0xd2, - 0xac, 0x03, 0x5e, 0x2c, 0x4b, 0x83, 0x41, 0xe3, 0xb2, 0x31, 0x6c, 0x47, 0x2e, 0x60, 0x3d, 0x68, - 0x93, 0x96, 0xb9, 0x49, 0x94, 0xce, 0x82, 0x13, 0xab, 0xec, 0x12, 0x2c, 0x80, 0x56, 0x86, 0xc6, - 0xc8, 0x14, 0x83, 0xa6, 0xd5, 0xea, 0x90, 0xf5, 0xc1, 0x37, 0x24, 0xe3, 0xd5, 0x8c, 0xb4, 0x8c, - 0x31, 0x38, 0xb5, 0x2a, 0xd8, 0xd4, 0xb4, 0xca, 0x0c, 0x9e, 0xa0, 0xf5, 0x40, 0x84, 0x59, 0x41, - 0x2c, 0x84, 0x73, 0xe9, 0x9e, 0xc6, 0x36, 0xf7, 0xa2, 0xbf, 0x98, 0x0d, 0xc1, 0xc3, 0x6a, 0x3c, - 0xdb, 0xdb, 0x1f, 0x31, 0xbe, 0xb7, 0x3a, 0xb7, 0x83, 0x47, 0x0e, 0x18, 0xfc, 0x34, 0x80, 0x1d, - 0x5f, 0x8c, 0x5d, 0x43, 0x53, 0xab, 0x8d, 0xfd, 0xd7, 0x1f, 0x75, 0x0f, 0xca, 0x77, 0x74, 0x54, - 0x31, 0xec, 0x0e, 0x2e, 0xd6, 0xd2, 0xd0, 0x6c, 0xdb, 0x7c, 0xdb, 0xb2, 0x73, 0x50, 0xb3, 0x9d, - 0x39, 0xf2, 0x2b, 0xb2, 0x5e, 0xe0, 0x1e, 0x20, 0x59, 0x6a, 0x43, 0x33, 0x83, 0x98, 0xdb, 0x4b, - 0xf8, 0xa3, 0x90, 0x3b, 0x5f, 0x78, 0xed, 0x0b, 0x9f, 0xd6, 0xbe, 0x44, 0x6d, 0x4b, 0xbf, 0x20, - 0xe6, 0xe3, 0x57, 0xd8, 0x37, 0x73, 0xdc, 0x3d, 0xde, 0xe0, 0xb9, 0xaa, 0x7f, 0xbb, 0x49, 0x97, - 0xf4, 0x5e, 0xce, 0x79, 0xac, 0x32, 0x91, 0xaa, 0x0f, 0x5c, 0x09, 0xe7, 0xaa, 0x59, 0xac, 0x44, - 0xaa, 0x9c, 0xf9, 0x46, 0xec, 0x39, 0x3d, 0x3f, 0xb3, 0xb9, 0xdb, 0xdf, 0x00, 0x00, 0x00, 0xff, - 0xff, 0x9d, 0x45, 0x64, 0x53, 0x53, 0x02, 0x00, 0x00, +var File_feast_types_FeatureRowExtended_proto protoreflect.FileDescriptor + +var file_feast_types_FeatureRowExtended_proto_rawDesc = []byte{ + 0x0a, 0x24, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x46, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x6f, 0x77, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x2f, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x6f, 0x77, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x22, 0x76, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x63, + 0x61, 0x75, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x61, 0x75, 0x73, + 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x12, + 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, + 0x63, 0x6b, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x73, 0x74, 0x61, 0x63, 0x6b, 0x54, 0x72, 0x61, 0x63, 0x65, 0x22, 0x4f, 0x0a, 0x07, 0x41, 0x74, + 0x74, 0x65, 0x6d, 0x70, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, + 0x73, 0x12, 0x28, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x12, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x45, + 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xb3, 0x01, 0x0a, 0x12, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x6f, 0x77, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, + 0x65, 0x64, 0x12, 0x29, 0x0a, 0x03, 0x72, 0x6f, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x17, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x46, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x6f, 0x77, 0x52, 0x03, 0x72, 0x6f, 0x77, 0x12, 0x37, 0x0a, + 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x52, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x41, + 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, + 0x73, 0x65, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x66, 0x69, 0x72, 0x73, 0x74, 0x53, 0x65, 0x65, + 0x6e, 0x42, 0x62, 0x0a, 0x11, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x42, 0x17, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, + 0x6f, 0x77, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x5a, + 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x66, 0x65, 0x61, 0x73, + 0x74, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x73, 0x64, 0x6b, 0x2f, + 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_feast_types_FeatureRowExtended_proto_rawDescOnce sync.Once + file_feast_types_FeatureRowExtended_proto_rawDescData = file_feast_types_FeatureRowExtended_proto_rawDesc +) + +func file_feast_types_FeatureRowExtended_proto_rawDescGZIP() []byte { + file_feast_types_FeatureRowExtended_proto_rawDescOnce.Do(func() { + file_feast_types_FeatureRowExtended_proto_rawDescData = protoimpl.X.CompressGZIP(file_feast_types_FeatureRowExtended_proto_rawDescData) + }) + return file_feast_types_FeatureRowExtended_proto_rawDescData +} + +var file_feast_types_FeatureRowExtended_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_feast_types_FeatureRowExtended_proto_goTypes = []interface{}{ + (*Error)(nil), // 0: feast.types.Error + (*Attempt)(nil), // 1: feast.types.Attempt + (*FeatureRowExtended)(nil), // 2: feast.types.FeatureRowExtended + (*FeatureRow)(nil), // 3: feast.types.FeatureRow + (*timestamp.Timestamp)(nil), // 4: google.protobuf.Timestamp +} +var file_feast_types_FeatureRowExtended_proto_depIdxs = []int32{ + 0, // 0: feast.types.Attempt.error:type_name -> feast.types.Error + 3, // 1: feast.types.FeatureRowExtended.row:type_name -> feast.types.FeatureRow + 1, // 2: feast.types.FeatureRowExtended.last_attempt:type_name -> feast.types.Attempt + 4, // 3: feast.types.FeatureRowExtended.first_seen:type_name -> google.protobuf.Timestamp + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_feast_types_FeatureRowExtended_proto_init() } +func file_feast_types_FeatureRowExtended_proto_init() { + if File_feast_types_FeatureRowExtended_proto != nil { + return + } + file_feast_types_FeatureRow_proto_init() + if !protoimpl.UnsafeEnabled { + file_feast_types_FeatureRowExtended_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Error); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_types_FeatureRowExtended_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Attempt); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_types_FeatureRowExtended_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FeatureRowExtended); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_feast_types_FeatureRowExtended_proto_rawDesc, + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_feast_types_FeatureRowExtended_proto_goTypes, + DependencyIndexes: file_feast_types_FeatureRowExtended_proto_depIdxs, + MessageInfos: file_feast_types_FeatureRowExtended_proto_msgTypes, + }.Build() + File_feast_types_FeatureRowExtended_proto = out.File + file_feast_types_FeatureRowExtended_proto_rawDesc = nil + file_feast_types_FeatureRowExtended_proto_goTypes = nil + file_feast_types_FeatureRowExtended_proto_depIdxs = nil } diff --git a/sdk/go/protos/feast/types/Field.pb.go b/sdk/go/protos/feast/types/Field.pb.go index 345b5259997..901075e2620 100644 --- a/sdk/go/protos/feast/types/Field.pb.go +++ b/sdk/go/protos/feast/types/Field.pb.go @@ -1,89 +1,182 @@ +// +// Copyright 2018 The Feast Authors +// +// 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.23.0 +// protoc v3.10.0 // source: feast/types/Field.proto package types import ( - fmt "fmt" proto "github.com/golang/protobuf/proto" - math "math" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" ) -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf +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 proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 type Field struct { - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Value *Value `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields -func (m *Field) Reset() { *m = Field{} } -func (m *Field) String() string { return proto.CompactTextString(m) } -func (*Field) ProtoMessage() {} -func (*Field) Descriptor() ([]byte, []int) { - return fileDescriptor_8c568a78dfaa9ca9, []int{0} + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Value *Value `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` } -func (m *Field) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Field.Unmarshal(m, b) -} -func (m *Field) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Field.Marshal(b, m, deterministic) -} -func (m *Field) XXX_Merge(src proto.Message) { - xxx_messageInfo_Field.Merge(m, src) +func (x *Field) Reset() { + *x = Field{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_types_Field_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *Field) XXX_Size() int { - return xxx_messageInfo_Field.Size(m) + +func (x *Field) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *Field) XXX_DiscardUnknown() { - xxx_messageInfo_Field.DiscardUnknown(m) + +func (*Field) ProtoMessage() {} + +func (x *Field) ProtoReflect() protoreflect.Message { + mi := &file_feast_types_Field_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_Field proto.InternalMessageInfo +// Deprecated: Use Field.ProtoReflect.Descriptor instead. +func (*Field) Descriptor() ([]byte, []int) { + return file_feast_types_Field_proto_rawDescGZIP(), []int{0} +} -func (m *Field) GetName() string { - if m != nil { - return m.Name +func (x *Field) GetName() string { + if x != nil { + return x.Name } return "" } -func (m *Field) GetValue() *Value { - if m != nil { - return m.Value +func (x *Field) GetValue() *Value { + if x != nil { + return x.Value } return nil } -func init() { - proto.RegisterType((*Field)(nil), "feast.types.Field") +var File_feast_types_Field_proto protoreflect.FileDescriptor + +var file_feast_types_Field_proto_rawDesc = []byte{ + 0x0a, 0x17, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x66, 0x65, 0x61, 0x73, 0x74, + 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x1a, 0x17, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x2f, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, + 0x45, 0x0a, 0x05, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x66, 0x65, + 0x61, 0x73, 0x74, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x55, 0x0a, 0x11, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x42, 0x0a, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x66, 0x65, + 0x61, 0x73, 0x74, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x73, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_feast_types_Field_proto_rawDescOnce sync.Once + file_feast_types_Field_proto_rawDescData = file_feast_types_Field_proto_rawDesc +) + +func file_feast_types_Field_proto_rawDescGZIP() []byte { + file_feast_types_Field_proto_rawDescOnce.Do(func() { + file_feast_types_Field_proto_rawDescData = protoimpl.X.CompressGZIP(file_feast_types_Field_proto_rawDescData) + }) + return file_feast_types_Field_proto_rawDescData } -func init() { proto.RegisterFile("feast/types/Field.proto", fileDescriptor_8c568a78dfaa9ca9) } - -var fileDescriptor_8c568a78dfaa9ca9 = []byte{ - // 165 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x4f, 0x4b, 0x4d, 0x2c, - 0x2e, 0xd1, 0x2f, 0xa9, 0x2c, 0x48, 0x2d, 0xd6, 0x77, 0xcb, 0x4c, 0xcd, 0x49, 0xd1, 0x2b, 0x28, - 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x06, 0x4b, 0xe8, 0x81, 0x25, 0xa4, 0x50, 0x54, 0x85, 0x25, 0xe6, - 0x94, 0xa6, 0x42, 0x54, 0x29, 0xb9, 0x72, 0xb1, 0x82, 0x35, 0x09, 0x09, 0x71, 0xb1, 0xe4, 0x25, - 0xe6, 0xa6, 0x4a, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x06, 0x81, 0xd9, 0x42, 0x1a, 0x5c, 0xac, 0x65, - 0x20, 0xb5, 0x12, 0x4c, 0x0a, 0x8c, 0x1a, 0xdc, 0x46, 0x42, 0x7a, 0x48, 0x46, 0xea, 0x81, 0x4d, - 0x09, 0x82, 0x28, 0x70, 0xf2, 0xe6, 0x42, 0xb6, 0xce, 0x89, 0x0b, 0x6c, 0x66, 0x00, 0xc8, 0x86, - 0x28, 0x83, 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0xfd, 0xf4, 0xfc, 0xac, - 0xd4, 0x6c, 0x7d, 0x88, 0x5b, 0x8a, 0x53, 0xb2, 0xf5, 0xd3, 0xf3, 0xf5, 0xc1, 0xce, 0x28, 0xd6, - 0x47, 0x72, 0x5f, 0x12, 0x1b, 0x58, 0xcc, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0xef, 0xe8, 0xff, - 0x05, 0xdb, 0x00, 0x00, 0x00, +var file_feast_types_Field_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_feast_types_Field_proto_goTypes = []interface{}{ + (*Field)(nil), // 0: feast.types.Field + (*Value)(nil), // 1: feast.types.Value +} +var file_feast_types_Field_proto_depIdxs = []int32{ + 1, // 0: feast.types.Field.value:type_name -> feast.types.Value + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_feast_types_Field_proto_init() } +func file_feast_types_Field_proto_init() { + if File_feast_types_Field_proto != nil { + return + } + file_feast_types_Value_proto_init() + if !protoimpl.UnsafeEnabled { + file_feast_types_Field_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Field); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_feast_types_Field_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_feast_types_Field_proto_goTypes, + DependencyIndexes: file_feast_types_Field_proto_depIdxs, + MessageInfos: file_feast_types_Field_proto_msgTypes, + }.Build() + File_feast_types_Field_proto = out.File + file_feast_types_Field_proto_rawDesc = nil + file_feast_types_Field_proto_goTypes = nil + file_feast_types_Field_proto_depIdxs = nil } diff --git a/sdk/go/protos/feast/types/Value.pb.go b/sdk/go/protos/feast/types/Value.pb.go index 3f9808b994f..317768fd549 100644 --- a/sdk/go/protos/feast/types/Value.pb.go +++ b/sdk/go/protos/feast/types/Value.pb.go @@ -1,24 +1,44 @@ +// +// Copyright 2018 The Feast Authors +// +// 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.23.0 +// protoc v3.10.0 // source: feast/types/Value.proto package types import ( - fmt "fmt" proto "github.com/golang/protobuf/proto" - math "math" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" ) -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf +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 proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 type ValueType_Enum int32 @@ -40,86 +60,118 @@ const ( ValueType_BOOL_LIST ValueType_Enum = 17 ) -var ValueType_Enum_name = map[int32]string{ - 0: "INVALID", - 1: "BYTES", - 2: "STRING", - 3: "INT32", - 4: "INT64", - 5: "DOUBLE", - 6: "FLOAT", - 7: "BOOL", - 11: "BYTES_LIST", - 12: "STRING_LIST", - 13: "INT32_LIST", - 14: "INT64_LIST", - 15: "DOUBLE_LIST", - 16: "FLOAT_LIST", - 17: "BOOL_LIST", -} - -var ValueType_Enum_value = map[string]int32{ - "INVALID": 0, - "BYTES": 1, - "STRING": 2, - "INT32": 3, - "INT64": 4, - "DOUBLE": 5, - "FLOAT": 6, - "BOOL": 7, - "BYTES_LIST": 11, - "STRING_LIST": 12, - "INT32_LIST": 13, - "INT64_LIST": 14, - "DOUBLE_LIST": 15, - "FLOAT_LIST": 16, - "BOOL_LIST": 17, +// Enum value maps for ValueType_Enum. +var ( + ValueType_Enum_name = map[int32]string{ + 0: "INVALID", + 1: "BYTES", + 2: "STRING", + 3: "INT32", + 4: "INT64", + 5: "DOUBLE", + 6: "FLOAT", + 7: "BOOL", + 11: "BYTES_LIST", + 12: "STRING_LIST", + 13: "INT32_LIST", + 14: "INT64_LIST", + 15: "DOUBLE_LIST", + 16: "FLOAT_LIST", + 17: "BOOL_LIST", + } + ValueType_Enum_value = map[string]int32{ + "INVALID": 0, + "BYTES": 1, + "STRING": 2, + "INT32": 3, + "INT64": 4, + "DOUBLE": 5, + "FLOAT": 6, + "BOOL": 7, + "BYTES_LIST": 11, + "STRING_LIST": 12, + "INT32_LIST": 13, + "INT64_LIST": 14, + "DOUBLE_LIST": 15, + "FLOAT_LIST": 16, + "BOOL_LIST": 17, + } +) + +func (x ValueType_Enum) Enum() *ValueType_Enum { + p := new(ValueType_Enum) + *p = x + return p } func (x ValueType_Enum) String() string { - return proto.EnumName(ValueType_Enum_name, int32(x)) + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } -func (ValueType_Enum) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_47c504407d284ecc, []int{0, 0} +func (ValueType_Enum) Descriptor() protoreflect.EnumDescriptor { + return file_feast_types_Value_proto_enumTypes[0].Descriptor() } -type ValueType struct { - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` +func (ValueType_Enum) Type() protoreflect.EnumType { + return &file_feast_types_Value_proto_enumTypes[0] } -func (m *ValueType) Reset() { *m = ValueType{} } -func (m *ValueType) String() string { return proto.CompactTextString(m) } -func (*ValueType) ProtoMessage() {} -func (*ValueType) Descriptor() ([]byte, []int) { - return fileDescriptor_47c504407d284ecc, []int{0} +func (x ValueType_Enum) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) } -func (m *ValueType) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_ValueType.Unmarshal(m, b) +// Deprecated: Use ValueType_Enum.Descriptor instead. +func (ValueType_Enum) EnumDescriptor() ([]byte, []int) { + return file_feast_types_Value_proto_rawDescGZIP(), []int{0, 0} } -func (m *ValueType) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_ValueType.Marshal(b, m, deterministic) + +type ValueType struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } -func (m *ValueType) XXX_Merge(src proto.Message) { - xxx_messageInfo_ValueType.Merge(m, src) + +func (x *ValueType) Reset() { + *x = ValueType{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_types_Value_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *ValueType) XXX_Size() int { - return xxx_messageInfo_ValueType.Size(m) + +func (x *ValueType) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *ValueType) XXX_DiscardUnknown() { - xxx_messageInfo_ValueType.DiscardUnknown(m) + +func (*ValueType) ProtoMessage() {} + +func (x *ValueType) ProtoReflect() protoreflect.Message { + mi := &file_feast_types_Value_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_ValueType proto.InternalMessageInfo +// Deprecated: Use ValueType.ProtoReflect.Descriptor instead. +func (*ValueType) Descriptor() ([]byte, []int) { + return file_feast_types_Value_proto_rawDescGZIP(), []int{0} +} type Value struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + // ValueType is referenced by the metadata types, FeatureInfo and EntityInfo. // The enum values do not have to match the oneof val field ids, but they should. // - // Types that are valid to be assigned to Val: + // Types that are assignable to Val: // *Value_BytesVal // *Value_StringVal // *Value_Int32Val @@ -134,36 +186,145 @@ type Value struct { // *Value_DoubleListVal // *Value_FloatListVal // *Value_BoolListVal - Val isValue_Val `protobuf_oneof:"val"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Val isValue_Val `protobuf_oneof:"val"` +} + +func (x *Value) Reset() { + *x = Value{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_types_Value_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Value) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *Value) Reset() { *m = Value{} } -func (m *Value) String() string { return proto.CompactTextString(m) } -func (*Value) ProtoMessage() {} +func (*Value) ProtoMessage() {} + +func (x *Value) ProtoReflect() protoreflect.Message { + mi := &file_feast_types_Value_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Value.ProtoReflect.Descriptor instead. func (*Value) Descriptor() ([]byte, []int) { - return fileDescriptor_47c504407d284ecc, []int{1} + return file_feast_types_Value_proto_rawDescGZIP(), []int{1} } -func (m *Value) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Value.Unmarshal(m, b) +func (m *Value) GetVal() isValue_Val { + if m != nil { + return m.Val + } + return nil } -func (m *Value) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Value.Marshal(b, m, deterministic) + +func (x *Value) GetBytesVal() []byte { + if x, ok := x.GetVal().(*Value_BytesVal); ok { + return x.BytesVal + } + return nil } -func (m *Value) XXX_Merge(src proto.Message) { - xxx_messageInfo_Value.Merge(m, src) + +func (x *Value) GetStringVal() string { + if x, ok := x.GetVal().(*Value_StringVal); ok { + return x.StringVal + } + return "" +} + +func (x *Value) GetInt32Val() int32 { + if x, ok := x.GetVal().(*Value_Int32Val); ok { + return x.Int32Val + } + return 0 +} + +func (x *Value) GetInt64Val() int64 { + if x, ok := x.GetVal().(*Value_Int64Val); ok { + return x.Int64Val + } + return 0 } -func (m *Value) XXX_Size() int { - return xxx_messageInfo_Value.Size(m) + +func (x *Value) GetDoubleVal() float64 { + if x, ok := x.GetVal().(*Value_DoubleVal); ok { + return x.DoubleVal + } + return 0 } -func (m *Value) XXX_DiscardUnknown() { - xxx_messageInfo_Value.DiscardUnknown(m) + +func (x *Value) GetFloatVal() float32 { + if x, ok := x.GetVal().(*Value_FloatVal); ok { + return x.FloatVal + } + return 0 } -var xxx_messageInfo_Value proto.InternalMessageInfo +func (x *Value) GetBoolVal() bool { + if x, ok := x.GetVal().(*Value_BoolVal); ok { + return x.BoolVal + } + return false +} + +func (x *Value) GetBytesListVal() *BytesList { + if x, ok := x.GetVal().(*Value_BytesListVal); ok { + return x.BytesListVal + } + return nil +} + +func (x *Value) GetStringListVal() *StringList { + if x, ok := x.GetVal().(*Value_StringListVal); ok { + return x.StringListVal + } + return nil +} + +func (x *Value) GetInt32ListVal() *Int32List { + if x, ok := x.GetVal().(*Value_Int32ListVal); ok { + return x.Int32ListVal + } + return nil +} + +func (x *Value) GetInt64ListVal() *Int64List { + if x, ok := x.GetVal().(*Value_Int64ListVal); ok { + return x.Int64ListVal + } + return nil +} + +func (x *Value) GetDoubleListVal() *DoubleList { + if x, ok := x.GetVal().(*Value_DoubleListVal); ok { + return x.DoubleListVal + } + return nil +} + +func (x *Value) GetFloatListVal() *FloatList { + if x, ok := x.GetVal().(*Value_FloatListVal); ok { + return x.FloatListVal + } + return nil +} + +func (x *Value) GetBoolListVal() *BoolList { + if x, ok := x.GetVal().(*Value_BoolListVal); ok { + return x.BoolListVal + } + return nil +} type isValue_Val interface { isValue_Val() @@ -253,457 +414,609 @@ func (*Value_FloatListVal) isValue_Val() {} func (*Value_BoolListVal) isValue_Val() {} -func (m *Value) GetVal() isValue_Val { - if m != nil { - return m.Val - } - return nil -} - -func (m *Value) GetBytesVal() []byte { - if x, ok := m.GetVal().(*Value_BytesVal); ok { - return x.BytesVal - } - return nil -} +type BytesList struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields -func (m *Value) GetStringVal() string { - if x, ok := m.GetVal().(*Value_StringVal); ok { - return x.StringVal - } - return "" + Val [][]byte `protobuf:"bytes,1,rep,name=val,proto3" json:"val,omitempty"` } -func (m *Value) GetInt32Val() int32 { - if x, ok := m.GetVal().(*Value_Int32Val); ok { - return x.Int32Val +func (x *BytesList) Reset() { + *x = BytesList{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_types_Value_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } - return 0 } -func (m *Value) GetInt64Val() int64 { - if x, ok := m.GetVal().(*Value_Int64Val); ok { - return x.Int64Val - } - return 0 +func (x *BytesList) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *Value) GetDoubleVal() float64 { - if x, ok := m.GetVal().(*Value_DoubleVal); ok { - return x.DoubleVal - } - return 0 -} +func (*BytesList) ProtoMessage() {} -func (m *Value) GetFloatVal() float32 { - if x, ok := m.GetVal().(*Value_FloatVal); ok { - return x.FloatVal +func (x *BytesList) ProtoReflect() protoreflect.Message { + mi := &file_feast_types_Value_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return 0 + return mi.MessageOf(x) } -func (m *Value) GetBoolVal() bool { - if x, ok := m.GetVal().(*Value_BoolVal); ok { - return x.BoolVal - } - return false +// Deprecated: Use BytesList.ProtoReflect.Descriptor instead. +func (*BytesList) Descriptor() ([]byte, []int) { + return file_feast_types_Value_proto_rawDescGZIP(), []int{2} } -func (m *Value) GetBytesListVal() *BytesList { - if x, ok := m.GetVal().(*Value_BytesListVal); ok { - return x.BytesListVal +func (x *BytesList) GetVal() [][]byte { + if x != nil { + return x.Val } return nil } -func (m *Value) GetStringListVal() *StringList { - if x, ok := m.GetVal().(*Value_StringListVal); ok { - return x.StringListVal - } - return nil +type StringList struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Val []string `protobuf:"bytes,1,rep,name=val,proto3" json:"val,omitempty"` } -func (m *Value) GetInt32ListVal() *Int32List { - if x, ok := m.GetVal().(*Value_Int32ListVal); ok { - return x.Int32ListVal +func (x *StringList) Reset() { + *x = StringList{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_types_Value_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } - return nil } -func (m *Value) GetInt64ListVal() *Int64List { - if x, ok := m.GetVal().(*Value_Int64ListVal); ok { - return x.Int64ListVal - } - return nil +func (x *StringList) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *Value) GetDoubleListVal() *DoubleList { - if x, ok := m.GetVal().(*Value_DoubleListVal); ok { - return x.DoubleListVal +func (*StringList) ProtoMessage() {} + +func (x *StringList) ProtoReflect() protoreflect.Message { + mi := &file_feast_types_Value_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return nil + return mi.MessageOf(x) } -func (m *Value) GetFloatListVal() *FloatList { - if x, ok := m.GetVal().(*Value_FloatListVal); ok { - return x.FloatListVal - } - return nil +// Deprecated: Use StringList.ProtoReflect.Descriptor instead. +func (*StringList) Descriptor() ([]byte, []int) { + return file_feast_types_Value_proto_rawDescGZIP(), []int{3} } -func (m *Value) GetBoolListVal() *BoolList { - if x, ok := m.GetVal().(*Value_BoolListVal); ok { - return x.BoolListVal +func (x *StringList) GetVal() []string { + if x != nil { + return x.Val } return nil } -// XXX_OneofWrappers is for the internal use of the proto package. -func (*Value) XXX_OneofWrappers() []interface{} { - return []interface{}{ - (*Value_BytesVal)(nil), - (*Value_StringVal)(nil), - (*Value_Int32Val)(nil), - (*Value_Int64Val)(nil), - (*Value_DoubleVal)(nil), - (*Value_FloatVal)(nil), - (*Value_BoolVal)(nil), - (*Value_BytesListVal)(nil), - (*Value_StringListVal)(nil), - (*Value_Int32ListVal)(nil), - (*Value_Int64ListVal)(nil), - (*Value_DoubleListVal)(nil), - (*Value_FloatListVal)(nil), - (*Value_BoolListVal)(nil), - } -} +type Int32List struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields -type BytesList struct { - Val [][]byte `protobuf:"bytes,1,rep,name=val,proto3" json:"val,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Val []int32 `protobuf:"varint,1,rep,packed,name=val,proto3" json:"val,omitempty"` } -func (m *BytesList) Reset() { *m = BytesList{} } -func (m *BytesList) String() string { return proto.CompactTextString(m) } -func (*BytesList) ProtoMessage() {} -func (*BytesList) Descriptor() ([]byte, []int) { - return fileDescriptor_47c504407d284ecc, []int{2} +func (x *Int32List) Reset() { + *x = Int32List{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_types_Value_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *BytesList) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_BytesList.Unmarshal(m, b) -} -func (m *BytesList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_BytesList.Marshal(b, m, deterministic) -} -func (m *BytesList) XXX_Merge(src proto.Message) { - xxx_messageInfo_BytesList.Merge(m, src) -} -func (m *BytesList) XXX_Size() int { - return xxx_messageInfo_BytesList.Size(m) -} -func (m *BytesList) XXX_DiscardUnknown() { - xxx_messageInfo_BytesList.DiscardUnknown(m) +func (x *Int32List) String() string { + return protoimpl.X.MessageStringOf(x) } -var xxx_messageInfo_BytesList proto.InternalMessageInfo +func (*Int32List) ProtoMessage() {} -func (m *BytesList) GetVal() [][]byte { - if m != nil { - return m.Val +func (x *Int32List) ProtoReflect() protoreflect.Message { + mi := &file_feast_types_Value_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return nil -} - -type StringList struct { - Val []string `protobuf:"bytes,1,rep,name=val,proto3" json:"val,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + return mi.MessageOf(x) } -func (m *StringList) Reset() { *m = StringList{} } -func (m *StringList) String() string { return proto.CompactTextString(m) } -func (*StringList) ProtoMessage() {} -func (*StringList) Descriptor() ([]byte, []int) { - return fileDescriptor_47c504407d284ecc, []int{3} -} - -func (m *StringList) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_StringList.Unmarshal(m, b) -} -func (m *StringList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_StringList.Marshal(b, m, deterministic) -} -func (m *StringList) XXX_Merge(src proto.Message) { - xxx_messageInfo_StringList.Merge(m, src) -} -func (m *StringList) XXX_Size() int { - return xxx_messageInfo_StringList.Size(m) -} -func (m *StringList) XXX_DiscardUnknown() { - xxx_messageInfo_StringList.DiscardUnknown(m) +// Deprecated: Use Int32List.ProtoReflect.Descriptor instead. +func (*Int32List) Descriptor() ([]byte, []int) { + return file_feast_types_Value_proto_rawDescGZIP(), []int{4} } -var xxx_messageInfo_StringList proto.InternalMessageInfo - -func (m *StringList) GetVal() []string { - if m != nil { - return m.Val +func (x *Int32List) GetVal() []int32 { + if x != nil { + return x.Val } return nil } -type Int32List struct { - Val []int32 `protobuf:"varint,1,rep,packed,name=val,proto3" json:"val,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} +type Int64List struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields -func (m *Int32List) Reset() { *m = Int32List{} } -func (m *Int32List) String() string { return proto.CompactTextString(m) } -func (*Int32List) ProtoMessage() {} -func (*Int32List) Descriptor() ([]byte, []int) { - return fileDescriptor_47c504407d284ecc, []int{4} + Val []int64 `protobuf:"varint,1,rep,packed,name=val,proto3" json:"val,omitempty"` } -func (m *Int32List) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Int32List.Unmarshal(m, b) -} -func (m *Int32List) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Int32List.Marshal(b, m, deterministic) -} -func (m *Int32List) XXX_Merge(src proto.Message) { - xxx_messageInfo_Int32List.Merge(m, src) -} -func (m *Int32List) XXX_Size() int { - return xxx_messageInfo_Int32List.Size(m) +func (x *Int64List) Reset() { + *x = Int64List{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_types_Value_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *Int32List) XXX_DiscardUnknown() { - xxx_messageInfo_Int32List.DiscardUnknown(m) + +func (x *Int64List) String() string { + return protoimpl.X.MessageStringOf(x) } -var xxx_messageInfo_Int32List proto.InternalMessageInfo +func (*Int64List) ProtoMessage() {} -func (m *Int32List) GetVal() []int32 { - if m != nil { - return m.Val +func (x *Int64List) ProtoReflect() protoreflect.Message { + mi := &file_feast_types_Value_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return nil -} - -type Int64List struct { - Val []int64 `protobuf:"varint,1,rep,packed,name=val,proto3" json:"val,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + return mi.MessageOf(x) } -func (m *Int64List) Reset() { *m = Int64List{} } -func (m *Int64List) String() string { return proto.CompactTextString(m) } -func (*Int64List) ProtoMessage() {} +// Deprecated: Use Int64List.ProtoReflect.Descriptor instead. func (*Int64List) Descriptor() ([]byte, []int) { - return fileDescriptor_47c504407d284ecc, []int{5} + return file_feast_types_Value_proto_rawDescGZIP(), []int{5} } -func (m *Int64List) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Int64List.Unmarshal(m, b) -} -func (m *Int64List) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Int64List.Marshal(b, m, deterministic) -} -func (m *Int64List) XXX_Merge(src proto.Message) { - xxx_messageInfo_Int64List.Merge(m, src) -} -func (m *Int64List) XXX_Size() int { - return xxx_messageInfo_Int64List.Size(m) -} -func (m *Int64List) XXX_DiscardUnknown() { - xxx_messageInfo_Int64List.DiscardUnknown(m) -} - -var xxx_messageInfo_Int64List proto.InternalMessageInfo - -func (m *Int64List) GetVal() []int64 { - if m != nil { - return m.Val +func (x *Int64List) GetVal() []int64 { + if x != nil { + return x.Val } return nil } type DoubleList struct { - Val []float64 `protobuf:"fixed64,1,rep,packed,name=val,proto3" json:"val,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields -func (m *DoubleList) Reset() { *m = DoubleList{} } -func (m *DoubleList) String() string { return proto.CompactTextString(m) } -func (*DoubleList) ProtoMessage() {} -func (*DoubleList) Descriptor() ([]byte, []int) { - return fileDescriptor_47c504407d284ecc, []int{6} + Val []float64 `protobuf:"fixed64,1,rep,packed,name=val,proto3" json:"val,omitempty"` } -func (m *DoubleList) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_DoubleList.Unmarshal(m, b) -} -func (m *DoubleList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_DoubleList.Marshal(b, m, deterministic) -} -func (m *DoubleList) XXX_Merge(src proto.Message) { - xxx_messageInfo_DoubleList.Merge(m, src) +func (x *DoubleList) Reset() { + *x = DoubleList{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_types_Value_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *DoubleList) XXX_Size() int { - return xxx_messageInfo_DoubleList.Size(m) + +func (x *DoubleList) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *DoubleList) XXX_DiscardUnknown() { - xxx_messageInfo_DoubleList.DiscardUnknown(m) + +func (*DoubleList) ProtoMessage() {} + +func (x *DoubleList) ProtoReflect() protoreflect.Message { + mi := &file_feast_types_Value_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_DoubleList proto.InternalMessageInfo +// Deprecated: Use DoubleList.ProtoReflect.Descriptor instead. +func (*DoubleList) Descriptor() ([]byte, []int) { + return file_feast_types_Value_proto_rawDescGZIP(), []int{6} +} -func (m *DoubleList) GetVal() []float64 { - if m != nil { - return m.Val +func (x *DoubleList) GetVal() []float64 { + if x != nil { + return x.Val } return nil } type FloatList struct { - Val []float32 `protobuf:"fixed32,1,rep,packed,name=val,proto3" json:"val,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields -func (m *FloatList) Reset() { *m = FloatList{} } -func (m *FloatList) String() string { return proto.CompactTextString(m) } -func (*FloatList) ProtoMessage() {} -func (*FloatList) Descriptor() ([]byte, []int) { - return fileDescriptor_47c504407d284ecc, []int{7} + Val []float32 `protobuf:"fixed32,1,rep,packed,name=val,proto3" json:"val,omitempty"` } -func (m *FloatList) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_FloatList.Unmarshal(m, b) -} -func (m *FloatList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_FloatList.Marshal(b, m, deterministic) -} -func (m *FloatList) XXX_Merge(src proto.Message) { - xxx_messageInfo_FloatList.Merge(m, src) +func (x *FloatList) Reset() { + *x = FloatList{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_types_Value_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *FloatList) XXX_Size() int { - return xxx_messageInfo_FloatList.Size(m) + +func (x *FloatList) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *FloatList) XXX_DiscardUnknown() { - xxx_messageInfo_FloatList.DiscardUnknown(m) + +func (*FloatList) ProtoMessage() {} + +func (x *FloatList) ProtoReflect() protoreflect.Message { + mi := &file_feast_types_Value_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_FloatList proto.InternalMessageInfo +// Deprecated: Use FloatList.ProtoReflect.Descriptor instead. +func (*FloatList) Descriptor() ([]byte, []int) { + return file_feast_types_Value_proto_rawDescGZIP(), []int{7} +} -func (m *FloatList) GetVal() []float32 { - if m != nil { - return m.Val +func (x *FloatList) GetVal() []float32 { + if x != nil { + return x.Val } return nil } type BoolList struct { - Val []bool `protobuf:"varint,1,rep,packed,name=val,proto3" json:"val,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields -func (m *BoolList) Reset() { *m = BoolList{} } -func (m *BoolList) String() string { return proto.CompactTextString(m) } -func (*BoolList) ProtoMessage() {} -func (*BoolList) Descriptor() ([]byte, []int) { - return fileDescriptor_47c504407d284ecc, []int{8} + Val []bool `protobuf:"varint,1,rep,packed,name=val,proto3" json:"val,omitempty"` } -func (m *BoolList) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_BoolList.Unmarshal(m, b) -} -func (m *BoolList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_BoolList.Marshal(b, m, deterministic) -} -func (m *BoolList) XXX_Merge(src proto.Message) { - xxx_messageInfo_BoolList.Merge(m, src) +func (x *BoolList) Reset() { + *x = BoolList{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_types_Value_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *BoolList) XXX_Size() int { - return xxx_messageInfo_BoolList.Size(m) + +func (x *BoolList) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *BoolList) XXX_DiscardUnknown() { - xxx_messageInfo_BoolList.DiscardUnknown(m) + +func (*BoolList) ProtoMessage() {} + +func (x *BoolList) ProtoReflect() protoreflect.Message { + mi := &file_feast_types_Value_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_BoolList proto.InternalMessageInfo +// Deprecated: Use BoolList.ProtoReflect.Descriptor instead. +func (*BoolList) Descriptor() ([]byte, []int) { + return file_feast_types_Value_proto_rawDescGZIP(), []int{8} +} -func (m *BoolList) GetVal() []bool { - if m != nil { - return m.Val +func (x *BoolList) GetVal() []bool { + if x != nil { + return x.Val } return nil } -func init() { - proto.RegisterEnum("feast.types.ValueType_Enum", ValueType_Enum_name, ValueType_Enum_value) - proto.RegisterType((*ValueType)(nil), "feast.types.ValueType") - proto.RegisterType((*Value)(nil), "feast.types.Value") - proto.RegisterType((*BytesList)(nil), "feast.types.BytesList") - proto.RegisterType((*StringList)(nil), "feast.types.StringList") - proto.RegisterType((*Int32List)(nil), "feast.types.Int32List") - proto.RegisterType((*Int64List)(nil), "feast.types.Int64List") - proto.RegisterType((*DoubleList)(nil), "feast.types.DoubleList") - proto.RegisterType((*FloatList)(nil), "feast.types.FloatList") - proto.RegisterType((*BoolList)(nil), "feast.types.BoolList") -} - -func init() { proto.RegisterFile("feast/types/Value.proto", fileDescriptor_47c504407d284ecc) } - -var fileDescriptor_47c504407d284ecc = []byte{ - // 600 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x94, 0xcf, 0x6e, 0x9b, 0x40, - 0x10, 0xc6, 0xbd, 0xc6, 0xd8, 0x30, 0xf8, 0xcf, 0x06, 0xa9, 0x4d, 0xa4, 0x36, 0x2d, 0xf2, 0x89, - 0x93, 0xa9, 0x12, 0xc4, 0xa5, 0x52, 0xa5, 0xa0, 0x24, 0x35, 0x2a, 0x8a, 0x2b, 0x4c, 0x2d, 0xb5, - 0x97, 0x08, 0x1a, 0xe2, 0xd2, 0x90, 0x10, 0x05, 0x1c, 0xc9, 0xef, 0xd4, 0xa7, 0xe9, 0x13, 0xf4, - 0x51, 0xaa, 0x9d, 0x5d, 0xd6, 0x44, 0xf2, 0xcd, 0xf3, 0xfd, 0xe6, 0xfb, 0xd8, 0xd9, 0x91, 0x17, - 0x0e, 0x6f, 0xb3, 0xa4, 0xaa, 0x9d, 0x7a, 0xfb, 0x98, 0x55, 0xce, 0x2a, 0x29, 0x36, 0xd9, 0xec, - 0xf1, 0xa9, 0xac, 0x4b, 0xd3, 0x40, 0x30, 0x43, 0x30, 0xfd, 0x47, 0x40, 0x47, 0x18, 0x6f, 0x1f, - 0xb3, 0xe9, 0x5f, 0x02, 0xbd, 0x8b, 0x87, 0xcd, 0xbd, 0x69, 0xc0, 0x20, 0xb8, 0x5a, 0x9d, 0x85, - 0xc1, 0x39, 0xed, 0x98, 0x3a, 0xa8, 0xfe, 0xf7, 0xf8, 0x62, 0x49, 0x89, 0x09, 0xd0, 0x5f, 0xc6, - 0x51, 0x70, 0xf5, 0x99, 0x76, 0x99, 0x1c, 0x5c, 0xc5, 0xa7, 0x27, 0x54, 0x11, 0x3f, 0x3d, 0x97, - 0xf6, 0x58, 0xc7, 0xf9, 0xe2, 0x9b, 0x1f, 0x5e, 0x50, 0x95, 0xc9, 0x97, 0xe1, 0xe2, 0x2c, 0xa6, - 0x7d, 0x53, 0x83, 0x9e, 0xbf, 0x58, 0x84, 0x74, 0x60, 0x8e, 0x01, 0x30, 0xed, 0x3a, 0x0c, 0x96, - 0x31, 0x35, 0xcc, 0x09, 0x18, 0x3c, 0x92, 0x0b, 0x43, 0xd6, 0x80, 0xb9, 0xbc, 0x1e, 0x89, 0xda, - 0x73, 0x79, 0x3d, 0x66, 0x06, 0xfe, 0x05, 0x2e, 0x4c, 0x58, 0x03, 0x7e, 0x86, 0xd7, 0xd4, 0x1c, - 0x81, 0xce, 0xbe, 0xc5, 0xcb, 0x83, 0xe9, 0x1f, 0x15, 0x54, 0x1c, 0xd1, 0x3c, 0x06, 0x3d, 0xdd, - 0xd6, 0x59, 0x75, 0xfd, 0x9c, 0x14, 0x47, 0xc4, 0x22, 0xf6, 0x70, 0xde, 0x89, 0x34, 0x94, 0x56, - 0x49, 0x61, 0xbe, 0x07, 0xa8, 0xea, 0xa7, 0xfc, 0x61, 0x8d, 0xbc, 0x6b, 0x11, 0x5b, 0x9f, 0x77, - 0x22, 0x9d, 0x6b, 0xac, 0xe1, 0x18, 0xf4, 0xfc, 0xa1, 0x3e, 0x3d, 0x41, 0xae, 0x58, 0xc4, 0x56, - 0x99, 0x1f, 0xa5, 0x1d, 0xf6, 0x5c, 0xc4, 0x3d, 0x8b, 0xd8, 0x8a, 0xc0, 0x9e, 0x2b, 0xe2, 0x6f, - 0xca, 0x4d, 0x5a, 0x64, 0xc8, 0x55, 0x8b, 0xd8, 0x84, 0xc5, 0x73, 0x4d, 0xf8, 0x6f, 0x8b, 0x32, - 0xa9, 0x91, 0xf7, 0x2d, 0x62, 0x77, 0x99, 0x1f, 0x25, 0x86, 0xdf, 0x80, 0x96, 0x96, 0x65, 0x81, - 0x74, 0x60, 0x11, 0x5b, 0x9b, 0x77, 0xa2, 0x01, 0x53, 0x18, 0xfc, 0x04, 0x63, 0x3e, 0x5a, 0x91, - 0x57, 0x3c, 0xc0, 0xb0, 0x88, 0x6d, 0x9c, 0xbc, 0x9e, 0xb5, 0xb6, 0x3d, 0xf3, 0x59, 0x4b, 0x98, - 0x57, 0xf5, 0xbc, 0x13, 0x0d, 0xd3, 0xa6, 0x60, 0xfe, 0x33, 0x98, 0x88, 0xd9, 0x65, 0xc0, 0x10, - 0x03, 0x0e, 0x5f, 0x04, 0x2c, 0xb1, 0x47, 0x24, 0x8c, 0x2a, 0x59, 0x89, 0x23, 0xf0, 0xdb, 0x91, - 0x09, 0xa3, 0x3d, 0x47, 0x08, 0x58, 0x4b, 0x73, 0x84, 0xbc, 0x29, 0x76, 0x7e, 0xcf, 0xdd, 0xf9, - 0xc7, 0xfb, 0xfd, 0x9e, 0xdb, 0xf2, 0xf3, 0x42, 0x8c, 0x20, 0xee, 0x57, 0x06, 0x4c, 0xf6, 0x8c, - 0x70, 0x8e, 0x3d, 0xcd, 0x08, 0x37, 0xb2, 0x12, 0x47, 0xe0, 0x1b, 0x90, 0x09, 0x74, 0xcf, 0x11, - 0x2e, 0x59, 0x4b, 0x73, 0x84, 0xdb, 0xa6, 0x60, 0xfe, 0x8f, 0x30, 0xc2, 0x15, 0x49, 0xfb, 0x01, - 0xda, 0x5f, 0xbd, 0x5c, 0x42, 0x59, 0x16, 0xc2, 0x6d, 0xa4, 0xe2, 0xf7, 0x2a, 0x29, 0x7c, 0x15, - 0x94, 0xe7, 0xa4, 0x98, 0x1e, 0x83, 0x2e, 0xd7, 0x64, 0x52, 0xd4, 0x8e, 0x88, 0xa5, 0xd8, 0xc3, - 0x08, 0xf1, 0x3b, 0x80, 0xdd, 0x12, 0xda, 0x5c, 0x8f, 0x1a, 0xbb, 0xbc, 0xe2, 0x36, 0x56, 0xdb, - 0x98, 0x5f, 0x5a, 0x1b, 0x2b, 0x32, 0x7d, 0x77, 0x3f, 0x6d, 0x4e, 0xa4, 0x5d, 0x4e, 0xdf, 0xc6, - 0x5d, 0x8e, 0xdf, 0x82, 0xd6, 0x4c, 0xd7, 0xa6, 0x1a, 0x52, 0xff, 0x0b, 0xb4, 0x9f, 0x1e, 0x1f, - 0xf0, 0x4f, 0xf9, 0x95, 0xbd, 0x49, 0x3f, 0x3e, 0xac, 0xf3, 0xfa, 0xd7, 0x26, 0x9d, 0xfd, 0x2c, - 0xef, 0x9d, 0x75, 0xf9, 0x3b, 0xbb, 0x73, 0xf8, 0xeb, 0x55, 0xdd, 0xdc, 0x39, 0xeb, 0xd2, 0xc1, - 0x87, 0xab, 0x72, 0x5a, 0x2f, 0x5a, 0xda, 0x47, 0xed, 0xf4, 0x7f, 0x00, 0x00, 0x00, 0xff, 0xff, - 0xa3, 0x3f, 0x67, 0x48, 0xe7, 0x04, 0x00, 0x00, +var File_feast_types_Value_proto protoreflect.FileDescriptor + +var file_feast_types_Value_proto_rawDesc = []byte{ + 0x0a, 0x17, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x66, 0x65, 0x61, 0x73, 0x74, + 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x22, 0xe0, 0x01, 0x0a, 0x09, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x22, 0xd2, 0x01, 0x0a, 0x04, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x0b, 0x0a, + 0x07, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x42, 0x59, + 0x54, 0x45, 0x53, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x10, + 0x02, 0x12, 0x09, 0x0a, 0x05, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, + 0x49, 0x4e, 0x54, 0x36, 0x34, 0x10, 0x04, 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x4f, 0x55, 0x42, 0x4c, + 0x45, 0x10, 0x05, 0x12, 0x09, 0x0a, 0x05, 0x46, 0x4c, 0x4f, 0x41, 0x54, 0x10, 0x06, 0x12, 0x08, + 0x0a, 0x04, 0x42, 0x4f, 0x4f, 0x4c, 0x10, 0x07, 0x12, 0x0e, 0x0a, 0x0a, 0x42, 0x59, 0x54, 0x45, + 0x53, 0x5f, 0x4c, 0x49, 0x53, 0x54, 0x10, 0x0b, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x54, 0x52, 0x49, + 0x4e, 0x47, 0x5f, 0x4c, 0x49, 0x53, 0x54, 0x10, 0x0c, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x4e, 0x54, + 0x33, 0x32, 0x5f, 0x4c, 0x49, 0x53, 0x54, 0x10, 0x0d, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x4e, 0x54, + 0x36, 0x34, 0x5f, 0x4c, 0x49, 0x53, 0x54, 0x10, 0x0e, 0x12, 0x0f, 0x0a, 0x0b, 0x44, 0x4f, 0x55, + 0x42, 0x4c, 0x45, 0x5f, 0x4c, 0x49, 0x53, 0x54, 0x10, 0x0f, 0x12, 0x0e, 0x0a, 0x0a, 0x46, 0x4c, + 0x4f, 0x41, 0x54, 0x5f, 0x4c, 0x49, 0x53, 0x54, 0x10, 0x10, 0x12, 0x0d, 0x0a, 0x09, 0x42, 0x4f, + 0x4f, 0x4c, 0x5f, 0x4c, 0x49, 0x53, 0x54, 0x10, 0x11, 0x22, 0xac, 0x05, 0x0a, 0x05, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x12, 0x1d, 0x0a, 0x09, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x76, 0x61, 0x6c, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x08, 0x62, 0x79, 0x74, 0x65, 0x73, 0x56, + 0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x76, 0x61, 0x6c, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x09, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x56, 0x61, 0x6c, 0x12, 0x1d, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x5f, 0x76, 0x61, 0x6c, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x56, + 0x61, 0x6c, 0x12, 0x1d, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x5f, 0x76, 0x61, 0x6c, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, + 0x6c, 0x12, 0x1f, 0x0a, 0x0a, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x01, 0x48, 0x00, 0x52, 0x09, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x56, + 0x61, 0x6c, 0x12, 0x1d, 0x0a, 0x09, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x02, 0x48, 0x00, 0x52, 0x08, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x56, 0x61, + 0x6c, 0x12, 0x1b, 0x0a, 0x08, 0x62, 0x6f, 0x6f, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x07, 0x62, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x12, 0x3e, + 0x0a, 0x0e, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x76, 0x61, 0x6c, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2e, 0x42, 0x79, 0x74, 0x65, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, + 0x52, 0x0c, 0x62, 0x79, 0x74, 0x65, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x12, 0x41, + 0x0a, 0x0f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x76, 0x61, + 0x6c, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, + 0x48, 0x00, 0x52, 0x0d, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, + 0x6c, 0x12, 0x3e, 0x0a, 0x0e, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x5f, + 0x76, 0x61, 0x6c, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x66, 0x65, 0x61, 0x73, + 0x74, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x4c, 0x69, 0x73, + 0x74, 0x48, 0x00, 0x52, 0x0c, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, + 0x6c, 0x12, 0x3e, 0x0a, 0x0e, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x5f, + 0x76, 0x61, 0x6c, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x66, 0x65, 0x61, 0x73, + 0x74, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x4c, 0x69, 0x73, + 0x74, 0x48, 0x00, 0x52, 0x0c, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, + 0x6c, 0x12, 0x41, 0x0a, 0x0f, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x5f, 0x6c, 0x69, 0x73, 0x74, + 0x5f, 0x76, 0x61, 0x6c, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x66, 0x65, 0x61, + 0x73, 0x74, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x4c, + 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x4c, 0x69, 0x73, + 0x74, 0x56, 0x61, 0x6c, 0x12, 0x3e, 0x0a, 0x0e, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x5f, 0x6c, 0x69, + 0x73, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x66, + 0x65, 0x61, 0x73, 0x74, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x46, 0x6c, 0x6f, 0x61, 0x74, + 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x4c, 0x69, 0x73, + 0x74, 0x56, 0x61, 0x6c, 0x12, 0x3b, 0x0a, 0x0d, 0x62, 0x6f, 0x6f, 0x6c, 0x5f, 0x6c, 0x69, 0x73, + 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x66, 0x65, + 0x61, 0x73, 0x74, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x4c, 0x69, + 0x73, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x62, 0x6f, 0x6f, 0x6c, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, + 0x6c, 0x42, 0x05, 0x0a, 0x03, 0x76, 0x61, 0x6c, 0x22, 0x1d, 0x0a, 0x09, 0x42, 0x79, 0x74, 0x65, + 0x73, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0c, 0x52, 0x03, 0x76, 0x61, 0x6c, 0x22, 0x1e, 0x0a, 0x0a, 0x53, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x03, 0x76, 0x61, 0x6c, 0x22, 0x1d, 0x0a, 0x09, 0x49, 0x6e, 0x74, 0x33, 0x32, + 0x4c, 0x69, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x05, 0x52, 0x03, 0x76, 0x61, 0x6c, 0x22, 0x1d, 0x0a, 0x09, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x4c, + 0x69, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, + 0x52, 0x03, 0x76, 0x61, 0x6c, 0x22, 0x1e, 0x0a, 0x0a, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x4c, + 0x69, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, 0x01, + 0x52, 0x03, 0x76, 0x61, 0x6c, 0x22, 0x1d, 0x0a, 0x09, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x4c, 0x69, + 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, 0x02, 0x52, + 0x03, 0x76, 0x61, 0x6c, 0x22, 0x1c, 0x0a, 0x08, 0x42, 0x6f, 0x6f, 0x6c, 0x4c, 0x69, 0x73, 0x74, + 0x12, 0x10, 0x0a, 0x03, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, 0x08, 0x52, 0x03, 0x76, + 0x61, 0x6c, 0x42, 0x55, 0x0a, 0x11, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x42, 0x0a, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x66, 0x65, 0x61, 0x73, 0x74, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, + 0x73, 0x64, 0x6b, 0x2f, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x66, 0x65, + 0x61, 0x73, 0x74, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +} + +var ( + file_feast_types_Value_proto_rawDescOnce sync.Once + file_feast_types_Value_proto_rawDescData = file_feast_types_Value_proto_rawDesc +) + +func file_feast_types_Value_proto_rawDescGZIP() []byte { + file_feast_types_Value_proto_rawDescOnce.Do(func() { + file_feast_types_Value_proto_rawDescData = protoimpl.X.CompressGZIP(file_feast_types_Value_proto_rawDescData) + }) + return file_feast_types_Value_proto_rawDescData +} + +var file_feast_types_Value_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_feast_types_Value_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_feast_types_Value_proto_goTypes = []interface{}{ + (ValueType_Enum)(0), // 0: feast.types.ValueType.Enum + (*ValueType)(nil), // 1: feast.types.ValueType + (*Value)(nil), // 2: feast.types.Value + (*BytesList)(nil), // 3: feast.types.BytesList + (*StringList)(nil), // 4: feast.types.StringList + (*Int32List)(nil), // 5: feast.types.Int32List + (*Int64List)(nil), // 6: feast.types.Int64List + (*DoubleList)(nil), // 7: feast.types.DoubleList + (*FloatList)(nil), // 8: feast.types.FloatList + (*BoolList)(nil), // 9: feast.types.BoolList +} +var file_feast_types_Value_proto_depIdxs = []int32{ + 3, // 0: feast.types.Value.bytes_list_val:type_name -> feast.types.BytesList + 4, // 1: feast.types.Value.string_list_val:type_name -> feast.types.StringList + 5, // 2: feast.types.Value.int32_list_val:type_name -> feast.types.Int32List + 6, // 3: feast.types.Value.int64_list_val:type_name -> feast.types.Int64List + 7, // 4: feast.types.Value.double_list_val:type_name -> feast.types.DoubleList + 8, // 5: feast.types.Value.float_list_val:type_name -> feast.types.FloatList + 9, // 6: feast.types.Value.bool_list_val:type_name -> feast.types.BoolList + 7, // [7:7] is the sub-list for method output_type + 7, // [7:7] is the sub-list for method input_type + 7, // [7:7] is the sub-list for extension type_name + 7, // [7:7] is the sub-list for extension extendee + 0, // [0:7] is the sub-list for field type_name +} + +func init() { file_feast_types_Value_proto_init() } +func file_feast_types_Value_proto_init() { + if File_feast_types_Value_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_feast_types_Value_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ValueType); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_types_Value_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Value); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_types_Value_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BytesList); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_types_Value_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StringList); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_types_Value_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Int32List); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_types_Value_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Int64List); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_types_Value_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DoubleList); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_types_Value_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FloatList); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_types_Value_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BoolList); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_feast_types_Value_proto_msgTypes[1].OneofWrappers = []interface{}{ + (*Value_BytesVal)(nil), + (*Value_StringVal)(nil), + (*Value_Int32Val)(nil), + (*Value_Int64Val)(nil), + (*Value_DoubleVal)(nil), + (*Value_FloatVal)(nil), + (*Value_BoolVal)(nil), + (*Value_BytesListVal)(nil), + (*Value_StringListVal)(nil), + (*Value_Int32ListVal)(nil), + (*Value_Int64ListVal)(nil), + (*Value_DoubleListVal)(nil), + (*Value_FloatListVal)(nil), + (*Value_BoolListVal)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_feast_types_Value_proto_rawDesc, + NumEnums: 1, + NumMessages: 9, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_feast_types_Value_proto_goTypes, + DependencyIndexes: file_feast_types_Value_proto_depIdxs, + EnumInfos: file_feast_types_Value_proto_enumTypes, + MessageInfos: file_feast_types_Value_proto_msgTypes, + }.Build() + File_feast_types_Value_proto = out.File + file_feast_types_Value_proto_rawDesc = nil + file_feast_types_Value_proto_goTypes = nil + file_feast_types_Value_proto_depIdxs = nil } diff --git a/sdk/go/protos/tensorflow_metadata/proto/v0/path.pb.go b/sdk/go/protos/tensorflow_metadata/proto/v0/path.pb.go new file mode 100644 index 00000000000..fe4cc3ee47a --- /dev/null +++ b/sdk/go/protos/tensorflow_metadata/proto/v0/path.pb.go @@ -0,0 +1,186 @@ +// Copyright 2018 The TensorFlow Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================= + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.23.0 +// protoc v3.10.0 +// source: tensorflow_metadata/proto/v0/path.proto + +package v0 + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +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 that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +// A path is a more general substitute for the name of a field or feature that +// can be used for flat examples as well as structured data. For example, if +// we had data in a protocol buffer: +// message Person { +// int age = 1; +// optional string gender = 2; +// repeated Person parent = 3; +// } +// Thus, here the path {step:["parent", "age"]} in statistics would refer to the +// age of a parent, and {step:["parent", "parent", "age"]} would refer to the +// age of a grandparent. This allows us to distinguish between the statistics +// of parents' ages and grandparents' ages. In general, repeated messages are +// to be preferred to linked lists of arbitrary length. +// For SequenceExample, if we have a feature list "foo", this is represented +// by {step:["##SEQUENCE##", "foo"]}. +type Path struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Any string is a valid step. + // However, whenever possible have a step be [A-Za-z0-9_]+. + Step []string `protobuf:"bytes,1,rep,name=step" json:"step,omitempty"` +} + +func (x *Path) Reset() { + *x = Path{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_path_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Path) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Path) ProtoMessage() {} + +func (x *Path) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_path_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Path.ProtoReflect.Descriptor instead. +func (*Path) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_path_proto_rawDescGZIP(), []int{0} +} + +func (x *Path) GetStep() []string { + if x != nil { + return x.Step + } + return nil +} + +var File_tensorflow_metadata_proto_v0_path_proto protoreflect.FileDescriptor + +var file_tensorflow_metadata_proto_v0_path_proto_rawDesc = []byte{ + 0x0a, 0x27, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x30, 0x2f, 0x70, + 0x61, 0x74, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x16, 0x74, 0x65, 0x6e, 0x73, 0x6f, + 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, + 0x30, 0x22, 0x1a, 0x0a, 0x04, 0x50, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x74, 0x65, + 0x70, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x73, 0x74, 0x65, 0x70, 0x42, 0x68, 0x0a, + 0x1a, 0x6f, 0x72, 0x67, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x50, 0x01, 0x5a, 0x45, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2d, + 0x64, 0x65, 0x76, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x67, 0x6f, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, + 0x6f, 0x77, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2f, 0x76, 0x30, 0xf8, 0x01, 0x01, +} + +var ( + file_tensorflow_metadata_proto_v0_path_proto_rawDescOnce sync.Once + file_tensorflow_metadata_proto_v0_path_proto_rawDescData = file_tensorflow_metadata_proto_v0_path_proto_rawDesc +) + +func file_tensorflow_metadata_proto_v0_path_proto_rawDescGZIP() []byte { + file_tensorflow_metadata_proto_v0_path_proto_rawDescOnce.Do(func() { + file_tensorflow_metadata_proto_v0_path_proto_rawDescData = protoimpl.X.CompressGZIP(file_tensorflow_metadata_proto_v0_path_proto_rawDescData) + }) + return file_tensorflow_metadata_proto_v0_path_proto_rawDescData +} + +var file_tensorflow_metadata_proto_v0_path_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_tensorflow_metadata_proto_v0_path_proto_goTypes = []interface{}{ + (*Path)(nil), // 0: tensorflow.metadata.v0.Path +} +var file_tensorflow_metadata_proto_v0_path_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_tensorflow_metadata_proto_v0_path_proto_init() } +func file_tensorflow_metadata_proto_v0_path_proto_init() { + if File_tensorflow_metadata_proto_v0_path_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_tensorflow_metadata_proto_v0_path_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Path); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_tensorflow_metadata_proto_v0_path_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_tensorflow_metadata_proto_v0_path_proto_goTypes, + DependencyIndexes: file_tensorflow_metadata_proto_v0_path_proto_depIdxs, + MessageInfos: file_tensorflow_metadata_proto_v0_path_proto_msgTypes, + }.Build() + File_tensorflow_metadata_proto_v0_path_proto = out.File + file_tensorflow_metadata_proto_v0_path_proto_rawDesc = nil + file_tensorflow_metadata_proto_v0_path_proto_goTypes = nil + file_tensorflow_metadata_proto_v0_path_proto_depIdxs = nil +} diff --git a/sdk/go/protos/tensorflow_metadata/proto/v0/schema.pb.go b/sdk/go/protos/tensorflow_metadata/proto/v0/schema.pb.go new file mode 100644 index 00000000000..97117947b78 --- /dev/null +++ b/sdk/go/protos/tensorflow_metadata/proto/v0/schema.pb.go @@ -0,0 +1,4083 @@ +// Copyright 2017 The TensorFlow Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================= + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.23.0 +// protoc v3.10.0 +// source: tensorflow_metadata/proto/v0/schema.proto + +package v0 + +import ( + proto "github.com/golang/protobuf/proto" + any "github.com/golang/protobuf/ptypes/any" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +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 that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +// LifecycleStage. Only UNKNOWN_STAGE, BETA, and PRODUCTION features are +// actually validated. +// PLANNED, ALPHA, and DEBUG are treated as DEPRECATED. +type LifecycleStage int32 + +const ( + LifecycleStage_UNKNOWN_STAGE LifecycleStage = 0 // Unknown stage. + LifecycleStage_PLANNED LifecycleStage = 1 // Planned feature, may not be created yet. + LifecycleStage_ALPHA LifecycleStage = 2 // Prototype feature, not used in experiments yet. + LifecycleStage_BETA LifecycleStage = 3 // Used in user-facing experiments. + LifecycleStage_PRODUCTION LifecycleStage = 4 // Used in a significant fraction of user traffic. + LifecycleStage_DEPRECATED LifecycleStage = 5 // No longer supported: do not use in new models. + LifecycleStage_DEBUG_ONLY LifecycleStage = 6 // Only exists for debugging purposes. +) + +// Enum value maps for LifecycleStage. +var ( + LifecycleStage_name = map[int32]string{ + 0: "UNKNOWN_STAGE", + 1: "PLANNED", + 2: "ALPHA", + 3: "BETA", + 4: "PRODUCTION", + 5: "DEPRECATED", + 6: "DEBUG_ONLY", + } + LifecycleStage_value = map[string]int32{ + "UNKNOWN_STAGE": 0, + "PLANNED": 1, + "ALPHA": 2, + "BETA": 3, + "PRODUCTION": 4, + "DEPRECATED": 5, + "DEBUG_ONLY": 6, + } +) + +func (x LifecycleStage) Enum() *LifecycleStage { + p := new(LifecycleStage) + *p = x + return p +} + +func (x LifecycleStage) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (LifecycleStage) Descriptor() protoreflect.EnumDescriptor { + return file_tensorflow_metadata_proto_v0_schema_proto_enumTypes[0].Descriptor() +} + +func (LifecycleStage) Type() protoreflect.EnumType { + return &file_tensorflow_metadata_proto_v0_schema_proto_enumTypes[0] +} + +func (x LifecycleStage) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *LifecycleStage) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = LifecycleStage(num) + return nil +} + +// Deprecated: Use LifecycleStage.Descriptor instead. +func (LifecycleStage) EnumDescriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{0} +} + +// Describes the physical representation of a feature. +// It may be different than the logical representation, which +// is represented as a Domain. +type FeatureType int32 + +const ( + FeatureType_TYPE_UNKNOWN FeatureType = 0 + FeatureType_BYTES FeatureType = 1 + FeatureType_INT FeatureType = 2 + FeatureType_FLOAT FeatureType = 3 + FeatureType_STRUCT FeatureType = 4 +) + +// Enum value maps for FeatureType. +var ( + FeatureType_name = map[int32]string{ + 0: "TYPE_UNKNOWN", + 1: "BYTES", + 2: "INT", + 3: "FLOAT", + 4: "STRUCT", + } + FeatureType_value = map[string]int32{ + "TYPE_UNKNOWN": 0, + "BYTES": 1, + "INT": 2, + "FLOAT": 3, + "STRUCT": 4, + } +) + +func (x FeatureType) Enum() *FeatureType { + p := new(FeatureType) + *p = x + return p +} + +func (x FeatureType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (FeatureType) Descriptor() protoreflect.EnumDescriptor { + return file_tensorflow_metadata_proto_v0_schema_proto_enumTypes[1].Descriptor() +} + +func (FeatureType) Type() protoreflect.EnumType { + return &file_tensorflow_metadata_proto_v0_schema_proto_enumTypes[1] +} + +func (x FeatureType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *FeatureType) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = FeatureType(num) + return nil +} + +// Deprecated: Use FeatureType.Descriptor instead. +func (FeatureType) EnumDescriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{1} +} + +type TimeDomain_IntegerTimeFormat int32 + +const ( + TimeDomain_FORMAT_UNKNOWN TimeDomain_IntegerTimeFormat = 0 + TimeDomain_UNIX_DAYS TimeDomain_IntegerTimeFormat = 5 // Number of days since 1970-01-01. + TimeDomain_UNIX_SECONDS TimeDomain_IntegerTimeFormat = 1 + TimeDomain_UNIX_MILLISECONDS TimeDomain_IntegerTimeFormat = 2 + TimeDomain_UNIX_MICROSECONDS TimeDomain_IntegerTimeFormat = 3 + TimeDomain_UNIX_NANOSECONDS TimeDomain_IntegerTimeFormat = 4 +) + +// Enum value maps for TimeDomain_IntegerTimeFormat. +var ( + TimeDomain_IntegerTimeFormat_name = map[int32]string{ + 0: "FORMAT_UNKNOWN", + 5: "UNIX_DAYS", + 1: "UNIX_SECONDS", + 2: "UNIX_MILLISECONDS", + 3: "UNIX_MICROSECONDS", + 4: "UNIX_NANOSECONDS", + } + TimeDomain_IntegerTimeFormat_value = map[string]int32{ + "FORMAT_UNKNOWN": 0, + "UNIX_DAYS": 5, + "UNIX_SECONDS": 1, + "UNIX_MILLISECONDS": 2, + "UNIX_MICROSECONDS": 3, + "UNIX_NANOSECONDS": 4, + } +) + +func (x TimeDomain_IntegerTimeFormat) Enum() *TimeDomain_IntegerTimeFormat { + p := new(TimeDomain_IntegerTimeFormat) + *p = x + return p +} + +func (x TimeDomain_IntegerTimeFormat) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (TimeDomain_IntegerTimeFormat) Descriptor() protoreflect.EnumDescriptor { + return file_tensorflow_metadata_proto_v0_schema_proto_enumTypes[2].Descriptor() +} + +func (TimeDomain_IntegerTimeFormat) Type() protoreflect.EnumType { + return &file_tensorflow_metadata_proto_v0_schema_proto_enumTypes[2] +} + +func (x TimeDomain_IntegerTimeFormat) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *TimeDomain_IntegerTimeFormat) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = TimeDomain_IntegerTimeFormat(num) + return nil +} + +// Deprecated: Use TimeDomain_IntegerTimeFormat.Descriptor instead. +func (TimeDomain_IntegerTimeFormat) EnumDescriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{19, 0} +} + +type TimeOfDayDomain_IntegerTimeOfDayFormat int32 + +const ( + TimeOfDayDomain_FORMAT_UNKNOWN TimeOfDayDomain_IntegerTimeOfDayFormat = 0 + // Time values, containing hour/minute/second/nanos, encoded into 8-byte + // bit fields following the ZetaSQL convention: + // 6 5 4 3 2 1 + // MSB 3210987654321098765432109876543210987654321098765432109876543210 LSB + // | H || M || S ||---------- nanos -----------| + TimeOfDayDomain_PACKED_64_NANOS TimeOfDayDomain_IntegerTimeOfDayFormat = 1 +) + +// Enum value maps for TimeOfDayDomain_IntegerTimeOfDayFormat. +var ( + TimeOfDayDomain_IntegerTimeOfDayFormat_name = map[int32]string{ + 0: "FORMAT_UNKNOWN", + 1: "PACKED_64_NANOS", + } + TimeOfDayDomain_IntegerTimeOfDayFormat_value = map[string]int32{ + "FORMAT_UNKNOWN": 0, + "PACKED_64_NANOS": 1, + } +) + +func (x TimeOfDayDomain_IntegerTimeOfDayFormat) Enum() *TimeOfDayDomain_IntegerTimeOfDayFormat { + p := new(TimeOfDayDomain_IntegerTimeOfDayFormat) + *p = x + return p +} + +func (x TimeOfDayDomain_IntegerTimeOfDayFormat) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (TimeOfDayDomain_IntegerTimeOfDayFormat) Descriptor() protoreflect.EnumDescriptor { + return file_tensorflow_metadata_proto_v0_schema_proto_enumTypes[3].Descriptor() +} + +func (TimeOfDayDomain_IntegerTimeOfDayFormat) Type() protoreflect.EnumType { + return &file_tensorflow_metadata_proto_v0_schema_proto_enumTypes[3] +} + +func (x TimeOfDayDomain_IntegerTimeOfDayFormat) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *TimeOfDayDomain_IntegerTimeOfDayFormat) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = TimeOfDayDomain_IntegerTimeOfDayFormat(num) + return nil +} + +// Deprecated: Use TimeOfDayDomain_IntegerTimeOfDayFormat.Descriptor instead. +func (TimeOfDayDomain_IntegerTimeOfDayFormat) EnumDescriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{20, 0} +} + +// +// Message to represent schema information. +// NextID: 14 +type Schema struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Features described in this schema. + Feature []*Feature `protobuf:"bytes,1,rep,name=feature" json:"feature,omitempty"` + // Sparse features described in this schema. + SparseFeature []*SparseFeature `protobuf:"bytes,6,rep,name=sparse_feature,json=sparseFeature" json:"sparse_feature,omitempty"` + // Weighted features described in this schema. + WeightedFeature []*WeightedFeature `protobuf:"bytes,12,rep,name=weighted_feature,json=weightedFeature" json:"weighted_feature,omitempty"` + // declared as top-level features in . + // String domains referenced in the features. + StringDomain []*StringDomain `protobuf:"bytes,4,rep,name=string_domain,json=stringDomain" json:"string_domain,omitempty"` + // top level float domains that can be reused by features + FloatDomain []*FloatDomain `protobuf:"bytes,9,rep,name=float_domain,json=floatDomain" json:"float_domain,omitempty"` + // top level int domains that can be reused by features + IntDomain []*IntDomain `protobuf:"bytes,10,rep,name=int_domain,json=intDomain" json:"int_domain,omitempty"` + // Default environments for each feature. + // An environment represents both a type of location (e.g. a server or phone) + // and a time (e.g. right before model X is run). In the standard scenario, + // 99% of the features should be in the default environments TRAINING, + // SERVING, and the LABEL (or labels) AND WEIGHT is only available at TRAINING + // (not at serving). + // Other possible variations: + // 1. There may be TRAINING_MOBILE, SERVING_MOBILE, TRAINING_SERVICE, + // and SERVING_SERVICE. + // 2. If one is ensembling three models, where the predictions of the first + // three models are available for the ensemble model, there may be + // TRAINING, SERVING_INITIAL, SERVING_ENSEMBLE. + // See FeatureProto::not_in_environment and FeatureProto::in_environment. + DefaultEnvironment []string `protobuf:"bytes,5,rep,name=default_environment,json=defaultEnvironment" json:"default_environment,omitempty"` + // Additional information about the schema as a whole. Features may also + // be annotated individually. + Annotation *Annotation `protobuf:"bytes,8,opt,name=annotation" json:"annotation,omitempty"` + // Dataset-level constraints. This is currently used for specifying + // information about changes in num_examples. + DatasetConstraints *DatasetConstraints `protobuf:"bytes,11,opt,name=dataset_constraints,json=datasetConstraints" json:"dataset_constraints,omitempty"` + // TensorRepresentation groups. The keys are the names of the groups. + // Key "" (empty string) denotes the "default" group, which is what should + // be used when a group name is not provided. + // See the documentation at TensorRepresentationGroup for more info. + // Under development. DO NOT USE. + TensorRepresentationGroup map[string]*TensorRepresentationGroup `protobuf:"bytes,13,rep,name=tensor_representation_group,json=tensorRepresentationGroup" json:"tensor_representation_group,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` +} + +func (x *Schema) Reset() { + *x = Schema{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Schema) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Schema) ProtoMessage() {} + +func (x *Schema) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Schema.ProtoReflect.Descriptor instead. +func (*Schema) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{0} +} + +func (x *Schema) GetFeature() []*Feature { + if x != nil { + return x.Feature + } + return nil +} + +func (x *Schema) GetSparseFeature() []*SparseFeature { + if x != nil { + return x.SparseFeature + } + return nil +} + +func (x *Schema) GetWeightedFeature() []*WeightedFeature { + if x != nil { + return x.WeightedFeature + } + return nil +} + +func (x *Schema) GetStringDomain() []*StringDomain { + if x != nil { + return x.StringDomain + } + return nil +} + +func (x *Schema) GetFloatDomain() []*FloatDomain { + if x != nil { + return x.FloatDomain + } + return nil +} + +func (x *Schema) GetIntDomain() []*IntDomain { + if x != nil { + return x.IntDomain + } + return nil +} + +func (x *Schema) GetDefaultEnvironment() []string { + if x != nil { + return x.DefaultEnvironment + } + return nil +} + +func (x *Schema) GetAnnotation() *Annotation { + if x != nil { + return x.Annotation + } + return nil +} + +func (x *Schema) GetDatasetConstraints() *DatasetConstraints { + if x != nil { + return x.DatasetConstraints + } + return nil +} + +func (x *Schema) GetTensorRepresentationGroup() map[string]*TensorRepresentationGroup { + if x != nil { + return x.TensorRepresentationGroup + } + return nil +} + +// Describes schema-level information about a specific feature. +// NextID: 31 +type Feature struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The name of the feature. + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` // required + // This field is no longer supported. Instead, use: + // lifecycle_stage: DEPRECATED + // TODO(b/111450258): remove this. + // + // Deprecated: Do not use. + Deprecated *bool `protobuf:"varint,2,opt,name=deprecated" json:"deprecated,omitempty"` + // Types that are assignable to PresenceConstraints: + // *Feature_Presence + // *Feature_GroupPresence + PresenceConstraints isFeature_PresenceConstraints `protobuf_oneof:"presence_constraints"` + // The shape of the feature which governs the number of values that appear in + // each example. + // + // Types that are assignable to ShapeType: + // *Feature_Shape + // *Feature_ValueCount + ShapeType isFeature_ShapeType `protobuf_oneof:"shape_type"` + // Physical type of the feature's values. + // Note that you can have: + // type: BYTES + // int_domain: { + // min: 0 + // max: 3 + // } + // This would be a field that is syntactically BYTES (i.e. strings), but + // semantically an int, i.e. it would be "0", "1", "2", or "3". + Type *FeatureType `protobuf:"varint,6,opt,name=type,enum=tensorflow.metadata.v0.FeatureType" json:"type,omitempty"` + // Domain for the values of the feature. + // + // Types that are assignable to DomainInfo: + // *Feature_Domain + // *Feature_IntDomain + // *Feature_FloatDomain + // *Feature_StringDomain + // *Feature_BoolDomain + // *Feature_StructDomain + // *Feature_NaturalLanguageDomain + // *Feature_ImageDomain + // *Feature_MidDomain + // *Feature_UrlDomain + // *Feature_TimeDomain + // *Feature_TimeOfDayDomain + DomainInfo isFeature_DomainInfo `protobuf_oneof:"domain_info"` + // Constraints on the distribution of the feature values. + // Currently only supported for StringDomains. + // TODO(b/69473628): Extend functionality to other domain types. + DistributionConstraints *DistributionConstraints `protobuf:"bytes,15,opt,name=distribution_constraints,json=distributionConstraints" json:"distribution_constraints,omitempty"` + // Additional information about the feature for documentation purpose. + Annotation *Annotation `protobuf:"bytes,16,opt,name=annotation" json:"annotation,omitempty"` + // Tests comparing the distribution to the associated serving data. + SkewComparator *FeatureComparator `protobuf:"bytes,18,opt,name=skew_comparator,json=skewComparator" json:"skew_comparator,omitempty"` + // Tests comparing the distribution between two consecutive spans (e.g. days). + DriftComparator *FeatureComparator `protobuf:"bytes,21,opt,name=drift_comparator,json=driftComparator" json:"drift_comparator,omitempty"` + // List of environments this feature is present in. + // Should be disjoint from not_in_environment. + // This feature is in environment "foo" if: + // ("foo" is in in_environment or default_environments) AND + // "foo" is not in not_in_environment. + // See Schema::default_environments. + InEnvironment []string `protobuf:"bytes,20,rep,name=in_environment,json=inEnvironment" json:"in_environment,omitempty"` + // List of environments this feature is not present in. + // Should be disjoint from of in_environment. + // See Schema::default_environments and in_environment. + NotInEnvironment []string `protobuf:"bytes,19,rep,name=not_in_environment,json=notInEnvironment" json:"not_in_environment,omitempty"` + // The lifecycle stage of a feature. It can also apply to its descendants. + // i.e., if a struct is DEPRECATED, its children are implicitly deprecated. + LifecycleStage *LifecycleStage `protobuf:"varint,22,opt,name=lifecycle_stage,json=lifecycleStage,enum=tensorflow.metadata.v0.LifecycleStage" json:"lifecycle_stage,omitempty"` +} + +func (x *Feature) Reset() { + *x = Feature{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Feature) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Feature) ProtoMessage() {} + +func (x *Feature) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Feature.ProtoReflect.Descriptor instead. +func (*Feature) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{1} +} + +func (x *Feature) GetName() string { + if x != nil && x.Name != nil { + return *x.Name + } + return "" +} + +// Deprecated: Do not use. +func (x *Feature) GetDeprecated() bool { + if x != nil && x.Deprecated != nil { + return *x.Deprecated + } + return false +} + +func (m *Feature) GetPresenceConstraints() isFeature_PresenceConstraints { + if m != nil { + return m.PresenceConstraints + } + return nil +} + +func (x *Feature) GetPresence() *FeaturePresence { + if x, ok := x.GetPresenceConstraints().(*Feature_Presence); ok { + return x.Presence + } + return nil +} + +func (x *Feature) GetGroupPresence() *FeaturePresenceWithinGroup { + if x, ok := x.GetPresenceConstraints().(*Feature_GroupPresence); ok { + return x.GroupPresence + } + return nil +} + +func (m *Feature) GetShapeType() isFeature_ShapeType { + if m != nil { + return m.ShapeType + } + return nil +} + +func (x *Feature) GetShape() *FixedShape { + if x, ok := x.GetShapeType().(*Feature_Shape); ok { + return x.Shape + } + return nil +} + +func (x *Feature) GetValueCount() *ValueCount { + if x, ok := x.GetShapeType().(*Feature_ValueCount); ok { + return x.ValueCount + } + return nil +} + +func (x *Feature) GetType() FeatureType { + if x != nil && x.Type != nil { + return *x.Type + } + return FeatureType_TYPE_UNKNOWN +} + +func (m *Feature) GetDomainInfo() isFeature_DomainInfo { + if m != nil { + return m.DomainInfo + } + return nil +} + +func (x *Feature) GetDomain() string { + if x, ok := x.GetDomainInfo().(*Feature_Domain); ok { + return x.Domain + } + return "" +} + +func (x *Feature) GetIntDomain() *IntDomain { + if x, ok := x.GetDomainInfo().(*Feature_IntDomain); ok { + return x.IntDomain + } + return nil +} + +func (x *Feature) GetFloatDomain() *FloatDomain { + if x, ok := x.GetDomainInfo().(*Feature_FloatDomain); ok { + return x.FloatDomain + } + return nil +} + +func (x *Feature) GetStringDomain() *StringDomain { + if x, ok := x.GetDomainInfo().(*Feature_StringDomain); ok { + return x.StringDomain + } + return nil +} + +func (x *Feature) GetBoolDomain() *BoolDomain { + if x, ok := x.GetDomainInfo().(*Feature_BoolDomain); ok { + return x.BoolDomain + } + return nil +} + +func (x *Feature) GetStructDomain() *StructDomain { + if x, ok := x.GetDomainInfo().(*Feature_StructDomain); ok { + return x.StructDomain + } + return nil +} + +func (x *Feature) GetNaturalLanguageDomain() *NaturalLanguageDomain { + if x, ok := x.GetDomainInfo().(*Feature_NaturalLanguageDomain); ok { + return x.NaturalLanguageDomain + } + return nil +} + +func (x *Feature) GetImageDomain() *ImageDomain { + if x, ok := x.GetDomainInfo().(*Feature_ImageDomain); ok { + return x.ImageDomain + } + return nil +} + +func (x *Feature) GetMidDomain() *MIDDomain { + if x, ok := x.GetDomainInfo().(*Feature_MidDomain); ok { + return x.MidDomain + } + return nil +} + +func (x *Feature) GetUrlDomain() *URLDomain { + if x, ok := x.GetDomainInfo().(*Feature_UrlDomain); ok { + return x.UrlDomain + } + return nil +} + +func (x *Feature) GetTimeDomain() *TimeDomain { + if x, ok := x.GetDomainInfo().(*Feature_TimeDomain); ok { + return x.TimeDomain + } + return nil +} + +func (x *Feature) GetTimeOfDayDomain() *TimeOfDayDomain { + if x, ok := x.GetDomainInfo().(*Feature_TimeOfDayDomain); ok { + return x.TimeOfDayDomain + } + return nil +} + +func (x *Feature) GetDistributionConstraints() *DistributionConstraints { + if x != nil { + return x.DistributionConstraints + } + return nil +} + +func (x *Feature) GetAnnotation() *Annotation { + if x != nil { + return x.Annotation + } + return nil +} + +func (x *Feature) GetSkewComparator() *FeatureComparator { + if x != nil { + return x.SkewComparator + } + return nil +} + +func (x *Feature) GetDriftComparator() *FeatureComparator { + if x != nil { + return x.DriftComparator + } + return nil +} + +func (x *Feature) GetInEnvironment() []string { + if x != nil { + return x.InEnvironment + } + return nil +} + +func (x *Feature) GetNotInEnvironment() []string { + if x != nil { + return x.NotInEnvironment + } + return nil +} + +func (x *Feature) GetLifecycleStage() LifecycleStage { + if x != nil && x.LifecycleStage != nil { + return *x.LifecycleStage + } + return LifecycleStage_UNKNOWN_STAGE +} + +type isFeature_PresenceConstraints interface { + isFeature_PresenceConstraints() +} + +type Feature_Presence struct { + // Constraints on the presence of this feature in the examples. + Presence *FeaturePresence `protobuf:"bytes,14,opt,name=presence,oneof"` +} + +type Feature_GroupPresence struct { + // Only used in the context of a "group" context, e.g., inside a sequence. + GroupPresence *FeaturePresenceWithinGroup `protobuf:"bytes,17,opt,name=group_presence,json=groupPresence,oneof"` +} + +func (*Feature_Presence) isFeature_PresenceConstraints() {} + +func (*Feature_GroupPresence) isFeature_PresenceConstraints() {} + +type isFeature_ShapeType interface { + isFeature_ShapeType() +} + +type Feature_Shape struct { + // The feature has a fixed shape corresponding to a multi-dimensional + // tensor. + Shape *FixedShape `protobuf:"bytes,23,opt,name=shape,oneof"` +} + +type Feature_ValueCount struct { + // The feature doesn't have a well defined shape. All we know are limits on + // the minimum and maximum number of values. + ValueCount *ValueCount `protobuf:"bytes,5,opt,name=value_count,json=valueCount,oneof"` +} + +func (*Feature_Shape) isFeature_ShapeType() {} + +func (*Feature_ValueCount) isFeature_ShapeType() {} + +type isFeature_DomainInfo interface { + isFeature_DomainInfo() +} + +type Feature_Domain struct { + // Reference to a domain defined at the schema level. + Domain string `protobuf:"bytes,7,opt,name=domain,oneof"` +} + +type Feature_IntDomain struct { + // Inline definitions of domains. + IntDomain *IntDomain `protobuf:"bytes,9,opt,name=int_domain,json=intDomain,oneof"` +} + +type Feature_FloatDomain struct { + FloatDomain *FloatDomain `protobuf:"bytes,10,opt,name=float_domain,json=floatDomain,oneof"` +} + +type Feature_StringDomain struct { + StringDomain *StringDomain `protobuf:"bytes,11,opt,name=string_domain,json=stringDomain,oneof"` +} + +type Feature_BoolDomain struct { + BoolDomain *BoolDomain `protobuf:"bytes,13,opt,name=bool_domain,json=boolDomain,oneof"` +} + +type Feature_StructDomain struct { + StructDomain *StructDomain `protobuf:"bytes,29,opt,name=struct_domain,json=structDomain,oneof"` +} + +type Feature_NaturalLanguageDomain struct { + // Supported semantic domains. + NaturalLanguageDomain *NaturalLanguageDomain `protobuf:"bytes,24,opt,name=natural_language_domain,json=naturalLanguageDomain,oneof"` +} + +type Feature_ImageDomain struct { + ImageDomain *ImageDomain `protobuf:"bytes,25,opt,name=image_domain,json=imageDomain,oneof"` +} + +type Feature_MidDomain struct { + MidDomain *MIDDomain `protobuf:"bytes,26,opt,name=mid_domain,json=midDomain,oneof"` +} + +type Feature_UrlDomain struct { + UrlDomain *URLDomain `protobuf:"bytes,27,opt,name=url_domain,json=urlDomain,oneof"` +} + +type Feature_TimeDomain struct { + TimeDomain *TimeDomain `protobuf:"bytes,28,opt,name=time_domain,json=timeDomain,oneof"` +} + +type Feature_TimeOfDayDomain struct { + TimeOfDayDomain *TimeOfDayDomain `protobuf:"bytes,30,opt,name=time_of_day_domain,json=timeOfDayDomain,oneof"` +} + +func (*Feature_Domain) isFeature_DomainInfo() {} + +func (*Feature_IntDomain) isFeature_DomainInfo() {} + +func (*Feature_FloatDomain) isFeature_DomainInfo() {} + +func (*Feature_StringDomain) isFeature_DomainInfo() {} + +func (*Feature_BoolDomain) isFeature_DomainInfo() {} + +func (*Feature_StructDomain) isFeature_DomainInfo() {} + +func (*Feature_NaturalLanguageDomain) isFeature_DomainInfo() {} + +func (*Feature_ImageDomain) isFeature_DomainInfo() {} + +func (*Feature_MidDomain) isFeature_DomainInfo() {} + +func (*Feature_UrlDomain) isFeature_DomainInfo() {} + +func (*Feature_TimeDomain) isFeature_DomainInfo() {} + +func (*Feature_TimeOfDayDomain) isFeature_DomainInfo() {} + +// Additional information about the schema or about a feature. +type Annotation struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Tags can be used to mark features. For example, tag on user_age feature can + // be `user_feature`, tag on user_country feature can be `location_feature`, + // `user_feature`. + Tag []string `protobuf:"bytes,1,rep,name=tag" json:"tag,omitempty"` + // Free-text comments. This can be used as a description of the feature, + // developer notes etc. + Comment []string `protobuf:"bytes,2,rep,name=comment" json:"comment,omitempty"` + // Application-specific metadata may be attached here. + ExtraMetadata []*any.Any `protobuf:"bytes,3,rep,name=extra_metadata,json=extraMetadata" json:"extra_metadata,omitempty"` +} + +func (x *Annotation) Reset() { + *x = Annotation{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Annotation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Annotation) ProtoMessage() {} + +func (x *Annotation) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Annotation.ProtoReflect.Descriptor instead. +func (*Annotation) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{2} +} + +func (x *Annotation) GetTag() []string { + if x != nil { + return x.Tag + } + return nil +} + +func (x *Annotation) GetComment() []string { + if x != nil { + return x.Comment + } + return nil +} + +func (x *Annotation) GetExtraMetadata() []*any.Any { + if x != nil { + return x.ExtraMetadata + } + return nil +} + +// Checks that the ratio of the current value to the previous value is not below +// the min_fraction_threshold or above the max_fraction_threshold. That is, +// previous value * min_fraction_threshold <= current value <= +// previous value * max_fraction_threshold. +// To specify that the value cannot change, set both min_fraction_threshold and +// max_fraction_threshold to 1.0. +type NumericValueComparator struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + MinFractionThreshold *float64 `protobuf:"fixed64,1,opt,name=min_fraction_threshold,json=minFractionThreshold" json:"min_fraction_threshold,omitempty"` + MaxFractionThreshold *float64 `protobuf:"fixed64,2,opt,name=max_fraction_threshold,json=maxFractionThreshold" json:"max_fraction_threshold,omitempty"` +} + +func (x *NumericValueComparator) Reset() { + *x = NumericValueComparator{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NumericValueComparator) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NumericValueComparator) ProtoMessage() {} + +func (x *NumericValueComparator) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NumericValueComparator.ProtoReflect.Descriptor instead. +func (*NumericValueComparator) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{3} +} + +func (x *NumericValueComparator) GetMinFractionThreshold() float64 { + if x != nil && x.MinFractionThreshold != nil { + return *x.MinFractionThreshold + } + return 0 +} + +func (x *NumericValueComparator) GetMaxFractionThreshold() float64 { + if x != nil && x.MaxFractionThreshold != nil { + return *x.MaxFractionThreshold + } + return 0 +} + +// Constraints on the entire dataset. +type DatasetConstraints struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Tests differences in number of examples between the current data and the + // previous span. + NumExamplesDriftComparator *NumericValueComparator `protobuf:"bytes,1,opt,name=num_examples_drift_comparator,json=numExamplesDriftComparator" json:"num_examples_drift_comparator,omitempty"` + // Tests comparisions in number of examples between the current data and the + // previous version of that data. + NumExamplesVersionComparator *NumericValueComparator `protobuf:"bytes,2,opt,name=num_examples_version_comparator,json=numExamplesVersionComparator" json:"num_examples_version_comparator,omitempty"` + // Minimum number of examples in the dataset. + MinExamplesCount *int64 `protobuf:"varint,3,opt,name=min_examples_count,json=minExamplesCount" json:"min_examples_count,omitempty"` +} + +func (x *DatasetConstraints) Reset() { + *x = DatasetConstraints{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DatasetConstraints) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DatasetConstraints) ProtoMessage() {} + +func (x *DatasetConstraints) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DatasetConstraints.ProtoReflect.Descriptor instead. +func (*DatasetConstraints) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{4} +} + +func (x *DatasetConstraints) GetNumExamplesDriftComparator() *NumericValueComparator { + if x != nil { + return x.NumExamplesDriftComparator + } + return nil +} + +func (x *DatasetConstraints) GetNumExamplesVersionComparator() *NumericValueComparator { + if x != nil { + return x.NumExamplesVersionComparator + } + return nil +} + +func (x *DatasetConstraints) GetMinExamplesCount() int64 { + if x != nil && x.MinExamplesCount != nil { + return *x.MinExamplesCount + } + return 0 +} + +// Specifies a fixed shape for the feature's values. The immediate implication +// is that each feature has a fixed number of values. Moreover, these values +// can be parsed in a multi-dimensional tensor using the specified axis sizes. +// The FixedShape defines a lexicographical ordering of the data. For instance, +// if there is a FixedShape { +// dim {size:3} dim {size:2} +// } +// then tensor[0][0]=field[0] +// then tensor[0][1]=field[1] +// then tensor[1][0]=field[2] +// then tensor[1][1]=field[3] +// then tensor[2][0]=field[4] +// then tensor[2][1]=field[5] +// +// The FixedShape message is identical with the TensorFlow TensorShape proto +// message. +type FixedShape struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The dimensions that define the shape. The total number of values in each + // example is the product of sizes of each dimension. + Dim []*FixedShape_Dim `protobuf:"bytes,2,rep,name=dim" json:"dim,omitempty"` +} + +func (x *FixedShape) Reset() { + *x = FixedShape{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FixedShape) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FixedShape) ProtoMessage() {} + +func (x *FixedShape) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FixedShape.ProtoReflect.Descriptor instead. +func (*FixedShape) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{5} +} + +func (x *FixedShape) GetDim() []*FixedShape_Dim { + if x != nil { + return x.Dim + } + return nil +} + +// Limits on maximum and minimum number of values in a +// single example (when the feature is present). Use this when the minimum +// value count can be different than the maximum value count. Otherwise prefer +// FixedShape. +type ValueCount struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Min *int64 `protobuf:"varint,1,opt,name=min" json:"min,omitempty"` + Max *int64 `protobuf:"varint,2,opt,name=max" json:"max,omitempty"` +} + +func (x *ValueCount) Reset() { + *x = ValueCount{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ValueCount) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ValueCount) ProtoMessage() {} + +func (x *ValueCount) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ValueCount.ProtoReflect.Descriptor instead. +func (*ValueCount) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{6} +} + +func (x *ValueCount) GetMin() int64 { + if x != nil && x.Min != nil { + return *x.Min + } + return 0 +} + +func (x *ValueCount) GetMax() int64 { + if x != nil && x.Max != nil { + return *x.Max + } + return 0 +} + +// Represents a weighted feature that is encoded as a combination of raw base +// features. The `weight_feature` should be a float feature with identical +// shape as the `feature`. This is useful for representing weights associated +// with categorical tokens (e.g. a TFIDF weight associated with each token). +// TODO(b/142122960): Handle WeightedCategorical end to end in TFX (validation, +// TFX Unit Testing, etc) +type WeightedFeature struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Name for the weighted feature. This should not clash with other features in + // the same schema. + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` // required + // Path of a base feature to be weighted. Required. + Feature *Path `protobuf:"bytes,2,opt,name=feature" json:"feature,omitempty"` + // Path of weight feature to associate with the base feature. Must be same + // shape as feature. Required. + WeightFeature *Path `protobuf:"bytes,3,opt,name=weight_feature,json=weightFeature" json:"weight_feature,omitempty"` + // The lifecycle_stage determines where a feature is expected to be used, + // and therefore how important issues with it are. + LifecycleStage *LifecycleStage `protobuf:"varint,4,opt,name=lifecycle_stage,json=lifecycleStage,enum=tensorflow.metadata.v0.LifecycleStage" json:"lifecycle_stage,omitempty"` +} + +func (x *WeightedFeature) Reset() { + *x = WeightedFeature{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WeightedFeature) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WeightedFeature) ProtoMessage() {} + +func (x *WeightedFeature) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WeightedFeature.ProtoReflect.Descriptor instead. +func (*WeightedFeature) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{7} +} + +func (x *WeightedFeature) GetName() string { + if x != nil && x.Name != nil { + return *x.Name + } + return "" +} + +func (x *WeightedFeature) GetFeature() *Path { + if x != nil { + return x.Feature + } + return nil +} + +func (x *WeightedFeature) GetWeightFeature() *Path { + if x != nil { + return x.WeightFeature + } + return nil +} + +func (x *WeightedFeature) GetLifecycleStage() LifecycleStage { + if x != nil && x.LifecycleStage != nil { + return *x.LifecycleStage + } + return LifecycleStage_UNKNOWN_STAGE +} + +// A sparse feature represents a sparse tensor that is encoded with a +// combination of raw features, namely index features and a value feature. Each +// index feature defines a list of indices in a different dimension. +type SparseFeature struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Name for the sparse feature. This should not clash with other features in + // the same schema. + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` // required + // This field is no longer supported. Instead, use: + // lifecycle_stage: DEPRECATED + // TODO(b/111450258): remove this. + // + // Deprecated: Do not use. + Deprecated *bool `protobuf:"varint,2,opt,name=deprecated" json:"deprecated,omitempty"` + // The lifecycle_stage determines where a feature is expected to be used, + // and therefore how important issues with it are. + LifecycleStage *LifecycleStage `protobuf:"varint,7,opt,name=lifecycle_stage,json=lifecycleStage,enum=tensorflow.metadata.v0.LifecycleStage" json:"lifecycle_stage,omitempty"` + // Constraints on the presence of this feature in examples. + // Deprecated, this is inferred by the referred features. + // + // Deprecated: Do not use. + Presence *FeaturePresence `protobuf:"bytes,4,opt,name=presence" json:"presence,omitempty"` + // Shape of the sparse tensor that this SparseFeature represents. + // Currently not supported. + // TODO(b/109669962): Consider deriving this from the referred features. + DenseShape *FixedShape `protobuf:"bytes,5,opt,name=dense_shape,json=denseShape" json:"dense_shape,omitempty"` + // Features that represent indexes. Should be integers >= 0. + IndexFeature []*SparseFeature_IndexFeature `protobuf:"bytes,6,rep,name=index_feature,json=indexFeature" json:"index_feature,omitempty"` // at least one + // If true then the index values are already sorted lexicographically. + IsSorted *bool `protobuf:"varint,8,opt,name=is_sorted,json=isSorted" json:"is_sorted,omitempty"` + ValueFeature *SparseFeature_ValueFeature `protobuf:"bytes,9,opt,name=value_feature,json=valueFeature" json:"value_feature,omitempty"` // required + // Type of value feature. + // Deprecated, this is inferred by the referred features. + // + // Deprecated: Do not use. + Type *FeatureType `protobuf:"varint,10,opt,name=type,enum=tensorflow.metadata.v0.FeatureType" json:"type,omitempty"` +} + +func (x *SparseFeature) Reset() { + *x = SparseFeature{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SparseFeature) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SparseFeature) ProtoMessage() {} + +func (x *SparseFeature) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SparseFeature.ProtoReflect.Descriptor instead. +func (*SparseFeature) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{8} +} + +func (x *SparseFeature) GetName() string { + if x != nil && x.Name != nil { + return *x.Name + } + return "" +} + +// Deprecated: Do not use. +func (x *SparseFeature) GetDeprecated() bool { + if x != nil && x.Deprecated != nil { + return *x.Deprecated + } + return false +} + +func (x *SparseFeature) GetLifecycleStage() LifecycleStage { + if x != nil && x.LifecycleStage != nil { + return *x.LifecycleStage + } + return LifecycleStage_UNKNOWN_STAGE +} + +// Deprecated: Do not use. +func (x *SparseFeature) GetPresence() *FeaturePresence { + if x != nil { + return x.Presence + } + return nil +} + +func (x *SparseFeature) GetDenseShape() *FixedShape { + if x != nil { + return x.DenseShape + } + return nil +} + +func (x *SparseFeature) GetIndexFeature() []*SparseFeature_IndexFeature { + if x != nil { + return x.IndexFeature + } + return nil +} + +func (x *SparseFeature) GetIsSorted() bool { + if x != nil && x.IsSorted != nil { + return *x.IsSorted + } + return false +} + +func (x *SparseFeature) GetValueFeature() *SparseFeature_ValueFeature { + if x != nil { + return x.ValueFeature + } + return nil +} + +// Deprecated: Do not use. +func (x *SparseFeature) GetType() FeatureType { + if x != nil && x.Type != nil { + return *x.Type + } + return FeatureType_TYPE_UNKNOWN +} + +// Models constraints on the distribution of a feature's values. +// TODO(martinz): replace min_domain_mass with max_off_domain (but slowly). +type DistributionConstraints struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The minimum fraction (in [0,1]) of values across all examples that + // should come from the feature's domain, e.g.: + // 1.0 => All values must come from the domain. + // .9 => At least 90% of the values must come from the domain. + MinDomainMass *float64 `protobuf:"fixed64,1,opt,name=min_domain_mass,json=minDomainMass,def=1" json:"min_domain_mass,omitempty"` +} + +// Default values for DistributionConstraints fields. +const ( + Default_DistributionConstraints_MinDomainMass = float64(1) +) + +func (x *DistributionConstraints) Reset() { + *x = DistributionConstraints{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DistributionConstraints) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DistributionConstraints) ProtoMessage() {} + +func (x *DistributionConstraints) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DistributionConstraints.ProtoReflect.Descriptor instead. +func (*DistributionConstraints) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{9} +} + +func (x *DistributionConstraints) GetMinDomainMass() float64 { + if x != nil && x.MinDomainMass != nil { + return *x.MinDomainMass + } + return Default_DistributionConstraints_MinDomainMass +} + +// Encodes information for domains of integer values. +// Note that FeatureType could be either INT or BYTES. +type IntDomain struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Id of the domain. Required if the domain is defined at the schema level. If + // so, then the name must be unique within the schema. + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + // Min and max values for the domain. + Min *int64 `protobuf:"varint,3,opt,name=min" json:"min,omitempty"` + Max *int64 `protobuf:"varint,4,opt,name=max" json:"max,omitempty"` + // If true then the domain encodes categorical values (i.e., ids) rather than + // ordinal values. + IsCategorical *bool `protobuf:"varint,5,opt,name=is_categorical,json=isCategorical" json:"is_categorical,omitempty"` +} + +func (x *IntDomain) Reset() { + *x = IntDomain{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *IntDomain) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IntDomain) ProtoMessage() {} + +func (x *IntDomain) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IntDomain.ProtoReflect.Descriptor instead. +func (*IntDomain) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{10} +} + +func (x *IntDomain) GetName() string { + if x != nil && x.Name != nil { + return *x.Name + } + return "" +} + +func (x *IntDomain) GetMin() int64 { + if x != nil && x.Min != nil { + return *x.Min + } + return 0 +} + +func (x *IntDomain) GetMax() int64 { + if x != nil && x.Max != nil { + return *x.Max + } + return 0 +} + +func (x *IntDomain) GetIsCategorical() bool { + if x != nil && x.IsCategorical != nil { + return *x.IsCategorical + } + return false +} + +// Encodes information for domains of float values. +// Note that FeatureType could be either INT or BYTES. +type FloatDomain struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Id of the domain. Required if the domain is defined at the schema level. If + // so, then the name must be unique within the schema. + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + // Min and max values of the domain. + Min *float32 `protobuf:"fixed32,3,opt,name=min" json:"min,omitempty"` + Max *float32 `protobuf:"fixed32,4,opt,name=max" json:"max,omitempty"` +} + +func (x *FloatDomain) Reset() { + *x = FloatDomain{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FloatDomain) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FloatDomain) ProtoMessage() {} + +func (x *FloatDomain) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FloatDomain.ProtoReflect.Descriptor instead. +func (*FloatDomain) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{11} +} + +func (x *FloatDomain) GetName() string { + if x != nil && x.Name != nil { + return *x.Name + } + return "" +} + +func (x *FloatDomain) GetMin() float32 { + if x != nil && x.Min != nil { + return *x.Min + } + return 0 +} + +func (x *FloatDomain) GetMax() float32 { + if x != nil && x.Max != nil { + return *x.Max + } + return 0 +} + +// Domain for a recursive struct. +// NOTE: If a feature with a StructDomain is deprecated, then all the +// child features (features and sparse_features of the StructDomain) are also +// considered to be deprecated. Similarly child features can only be in +// environments of the parent feature. +type StructDomain struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Feature []*Feature `protobuf:"bytes,1,rep,name=feature" json:"feature,omitempty"` + SparseFeature []*SparseFeature `protobuf:"bytes,2,rep,name=sparse_feature,json=sparseFeature" json:"sparse_feature,omitempty"` +} + +func (x *StructDomain) Reset() { + *x = StructDomain{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StructDomain) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StructDomain) ProtoMessage() {} + +func (x *StructDomain) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StructDomain.ProtoReflect.Descriptor instead. +func (*StructDomain) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{12} +} + +func (x *StructDomain) GetFeature() []*Feature { + if x != nil { + return x.Feature + } + return nil +} + +func (x *StructDomain) GetSparseFeature() []*SparseFeature { + if x != nil { + return x.SparseFeature + } + return nil +} + +// Encodes information for domains of string values. +type StringDomain struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Id of the domain. Required if the domain is defined at the schema level. If + // so, then the name must be unique within the schema. + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + // The values appearing in the domain. + Value []string `protobuf:"bytes,2,rep,name=value" json:"value,omitempty"` +} + +func (x *StringDomain) Reset() { + *x = StringDomain{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StringDomain) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StringDomain) ProtoMessage() {} + +func (x *StringDomain) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StringDomain.ProtoReflect.Descriptor instead. +func (*StringDomain) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{13} +} + +func (x *StringDomain) GetName() string { + if x != nil && x.Name != nil { + return *x.Name + } + return "" +} + +func (x *StringDomain) GetValue() []string { + if x != nil { + return x.Value + } + return nil +} + +// Encodes information about the domain of a boolean attribute that encodes its +// TRUE/FALSE values as strings, or 0=false, 1=true. +// Note that FeatureType could be either INT or BYTES. +type BoolDomain struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Id of the domain. Required if the domain is defined at the schema level. If + // so, then the name must be unique within the schema. + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + // Strings values for TRUE/FALSE. + TrueValue *string `protobuf:"bytes,2,opt,name=true_value,json=trueValue" json:"true_value,omitempty"` + FalseValue *string `protobuf:"bytes,3,opt,name=false_value,json=falseValue" json:"false_value,omitempty"` +} + +func (x *BoolDomain) Reset() { + *x = BoolDomain{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BoolDomain) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BoolDomain) ProtoMessage() {} + +func (x *BoolDomain) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BoolDomain.ProtoReflect.Descriptor instead. +func (*BoolDomain) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{14} +} + +func (x *BoolDomain) GetName() string { + if x != nil && x.Name != nil { + return *x.Name + } + return "" +} + +func (x *BoolDomain) GetTrueValue() string { + if x != nil && x.TrueValue != nil { + return *x.TrueValue + } + return "" +} + +func (x *BoolDomain) GetFalseValue() string { + if x != nil && x.FalseValue != nil { + return *x.FalseValue + } + return "" +} + +// Natural language text. +type NaturalLanguageDomain struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *NaturalLanguageDomain) Reset() { + *x = NaturalLanguageDomain{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NaturalLanguageDomain) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NaturalLanguageDomain) ProtoMessage() {} + +func (x *NaturalLanguageDomain) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NaturalLanguageDomain.ProtoReflect.Descriptor instead. +func (*NaturalLanguageDomain) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{15} +} + +// Image data. +type ImageDomain struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ImageDomain) Reset() { + *x = ImageDomain{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ImageDomain) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ImageDomain) ProtoMessage() {} + +func (x *ImageDomain) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ImageDomain.ProtoReflect.Descriptor instead. +func (*ImageDomain) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{16} +} + +// Knowledge graph ID, see: https://www.wikidata.org/wiki/Property:P646 +type MIDDomain struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *MIDDomain) Reset() { + *x = MIDDomain{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MIDDomain) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MIDDomain) ProtoMessage() {} + +func (x *MIDDomain) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MIDDomain.ProtoReflect.Descriptor instead. +func (*MIDDomain) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{17} +} + +// A URL, see: https://en.wikipedia.org/wiki/URL +type URLDomain struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *URLDomain) Reset() { + *x = URLDomain{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *URLDomain) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*URLDomain) ProtoMessage() {} + +func (x *URLDomain) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use URLDomain.ProtoReflect.Descriptor instead. +func (*URLDomain) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{18} +} + +// Time or date representation. +type TimeDomain struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Format: + // *TimeDomain_StringFormat + // *TimeDomain_IntegerFormat + Format isTimeDomain_Format `protobuf_oneof:"format"` +} + +func (x *TimeDomain) Reset() { + *x = TimeDomain{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TimeDomain) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TimeDomain) ProtoMessage() {} + +func (x *TimeDomain) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TimeDomain.ProtoReflect.Descriptor instead. +func (*TimeDomain) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{19} +} + +func (m *TimeDomain) GetFormat() isTimeDomain_Format { + if m != nil { + return m.Format + } + return nil +} + +func (x *TimeDomain) GetStringFormat() string { + if x, ok := x.GetFormat().(*TimeDomain_StringFormat); ok { + return x.StringFormat + } + return "" +} + +func (x *TimeDomain) GetIntegerFormat() TimeDomain_IntegerTimeFormat { + if x, ok := x.GetFormat().(*TimeDomain_IntegerFormat); ok { + return x.IntegerFormat + } + return TimeDomain_FORMAT_UNKNOWN +} + +type isTimeDomain_Format interface { + isTimeDomain_Format() +} + +type TimeDomain_StringFormat struct { + // Expected format that contains a combination of regular characters and + // special format specifiers. Format specifiers are a subset of the + // strptime standard. + StringFormat string `protobuf:"bytes,1,opt,name=string_format,json=stringFormat,oneof"` +} + +type TimeDomain_IntegerFormat struct { + // Expected format of integer times. + IntegerFormat TimeDomain_IntegerTimeFormat `protobuf:"varint,2,opt,name=integer_format,json=integerFormat,enum=tensorflow.metadata.v0.TimeDomain_IntegerTimeFormat,oneof"` +} + +func (*TimeDomain_StringFormat) isTimeDomain_Format() {} + +func (*TimeDomain_IntegerFormat) isTimeDomain_Format() {} + +// Time of day, without a particular date. +type TimeOfDayDomain struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Format: + // *TimeOfDayDomain_StringFormat + // *TimeOfDayDomain_IntegerFormat + Format isTimeOfDayDomain_Format `protobuf_oneof:"format"` +} + +func (x *TimeOfDayDomain) Reset() { + *x = TimeOfDayDomain{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TimeOfDayDomain) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TimeOfDayDomain) ProtoMessage() {} + +func (x *TimeOfDayDomain) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TimeOfDayDomain.ProtoReflect.Descriptor instead. +func (*TimeOfDayDomain) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{20} +} + +func (m *TimeOfDayDomain) GetFormat() isTimeOfDayDomain_Format { + if m != nil { + return m.Format + } + return nil +} + +func (x *TimeOfDayDomain) GetStringFormat() string { + if x, ok := x.GetFormat().(*TimeOfDayDomain_StringFormat); ok { + return x.StringFormat + } + return "" +} + +func (x *TimeOfDayDomain) GetIntegerFormat() TimeOfDayDomain_IntegerTimeOfDayFormat { + if x, ok := x.GetFormat().(*TimeOfDayDomain_IntegerFormat); ok { + return x.IntegerFormat + } + return TimeOfDayDomain_FORMAT_UNKNOWN +} + +type isTimeOfDayDomain_Format interface { + isTimeOfDayDomain_Format() +} + +type TimeOfDayDomain_StringFormat struct { + // Expected format that contains a combination of regular characters and + // special format specifiers. Format specifiers are a subset of the + // strptime standard. + StringFormat string `protobuf:"bytes,1,opt,name=string_format,json=stringFormat,oneof"` +} + +type TimeOfDayDomain_IntegerFormat struct { + // Expected format of integer times. + IntegerFormat TimeOfDayDomain_IntegerTimeOfDayFormat `protobuf:"varint,2,opt,name=integer_format,json=integerFormat,enum=tensorflow.metadata.v0.TimeOfDayDomain_IntegerTimeOfDayFormat,oneof"` +} + +func (*TimeOfDayDomain_StringFormat) isTimeOfDayDomain_Format() {} + +func (*TimeOfDayDomain_IntegerFormat) isTimeOfDayDomain_Format() {} + +// Describes constraints on the presence of the feature in the data. +type FeaturePresence struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Minimum fraction of examples that have this feature. + MinFraction *float64 `protobuf:"fixed64,1,opt,name=min_fraction,json=minFraction" json:"min_fraction,omitempty"` + // Minimum number of examples that have this feature. + MinCount *int64 `protobuf:"varint,2,opt,name=min_count,json=minCount" json:"min_count,omitempty"` +} + +func (x *FeaturePresence) Reset() { + *x = FeaturePresence{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FeaturePresence) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FeaturePresence) ProtoMessage() {} + +func (x *FeaturePresence) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[21] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FeaturePresence.ProtoReflect.Descriptor instead. +func (*FeaturePresence) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{21} +} + +func (x *FeaturePresence) GetMinFraction() float64 { + if x != nil && x.MinFraction != nil { + return *x.MinFraction + } + return 0 +} + +func (x *FeaturePresence) GetMinCount() int64 { + if x != nil && x.MinCount != nil { + return *x.MinCount + } + return 0 +} + +// Records constraints on the presence of a feature inside a "group" context +// (e.g., .presence inside a group of features that define a sequence). +type FeaturePresenceWithinGroup struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Required *bool `protobuf:"varint,1,opt,name=required" json:"required,omitempty"` +} + +func (x *FeaturePresenceWithinGroup) Reset() { + *x = FeaturePresenceWithinGroup{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FeaturePresenceWithinGroup) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FeaturePresenceWithinGroup) ProtoMessage() {} + +func (x *FeaturePresenceWithinGroup) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FeaturePresenceWithinGroup.ProtoReflect.Descriptor instead. +func (*FeaturePresenceWithinGroup) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{22} +} + +func (x *FeaturePresenceWithinGroup) GetRequired() bool { + if x != nil && x.Required != nil { + return *x.Required + } + return false +} + +// Checks that the L-infinity norm is below a certain threshold between the +// two discrete distributions. Since this is applied to a FeatureNameStatistics, +// it only considers the top k. +// L_infty(p,q) = max_i |p_i-q_i| +type InfinityNorm struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The InfinityNorm is in the interval [0.0, 1.0] so sensible bounds should + // be in the interval [0.0, 1.0). + Threshold *float64 `protobuf:"fixed64,1,opt,name=threshold" json:"threshold,omitempty"` +} + +func (x *InfinityNorm) Reset() { + *x = InfinityNorm{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *InfinityNorm) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InfinityNorm) ProtoMessage() {} + +func (x *InfinityNorm) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InfinityNorm.ProtoReflect.Descriptor instead. +func (*InfinityNorm) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{23} +} + +func (x *InfinityNorm) GetThreshold() float64 { + if x != nil && x.Threshold != nil { + return *x.Threshold + } + return 0 +} + +type FeatureComparator struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + InfinityNorm *InfinityNorm `protobuf:"bytes,1,opt,name=infinity_norm,json=infinityNorm" json:"infinity_norm,omitempty"` +} + +func (x *FeatureComparator) Reset() { + *x = FeatureComparator{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FeatureComparator) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FeatureComparator) ProtoMessage() {} + +func (x *FeatureComparator) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FeatureComparator.ProtoReflect.Descriptor instead. +func (*FeatureComparator) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{24} +} + +func (x *FeatureComparator) GetInfinityNorm() *InfinityNorm { + if x != nil { + return x.InfinityNorm + } + return nil +} + +// A TensorRepresentation captures the intent for converting columns in a +// dataset to TensorFlow Tensors (or more generally, tf.CompositeTensors). +// Note that one tf.CompositeTensor may consist of data from multiple columns, +// for example, a N-dimensional tf.SparseTensor may need N + 1 columns to +// provide the sparse indices and values. +// Note that the "column name" that a TensorRepresentation needs is a +// string, not a Path -- it means that the column name identifies a top-level +// Feature in the schema (i.e. you cannot specify a Feature nested in a STRUCT +// Feature). +type TensorRepresentation struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Kind: + // *TensorRepresentation_DenseTensor_ + // *TensorRepresentation_VarlenSparseTensor + // *TensorRepresentation_SparseTensor_ + Kind isTensorRepresentation_Kind `protobuf_oneof:"kind"` +} + +func (x *TensorRepresentation) Reset() { + *x = TensorRepresentation{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TensorRepresentation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TensorRepresentation) ProtoMessage() {} + +func (x *TensorRepresentation) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TensorRepresentation.ProtoReflect.Descriptor instead. +func (*TensorRepresentation) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{25} +} + +func (m *TensorRepresentation) GetKind() isTensorRepresentation_Kind { + if m != nil { + return m.Kind + } + return nil +} + +func (x *TensorRepresentation) GetDenseTensor() *TensorRepresentation_DenseTensor { + if x, ok := x.GetKind().(*TensorRepresentation_DenseTensor_); ok { + return x.DenseTensor + } + return nil +} + +func (x *TensorRepresentation) GetVarlenSparseTensor() *TensorRepresentation_VarLenSparseTensor { + if x, ok := x.GetKind().(*TensorRepresentation_VarlenSparseTensor); ok { + return x.VarlenSparseTensor + } + return nil +} + +func (x *TensorRepresentation) GetSparseTensor() *TensorRepresentation_SparseTensor { + if x, ok := x.GetKind().(*TensorRepresentation_SparseTensor_); ok { + return x.SparseTensor + } + return nil +} + +type isTensorRepresentation_Kind interface { + isTensorRepresentation_Kind() +} + +type TensorRepresentation_DenseTensor_ struct { + DenseTensor *TensorRepresentation_DenseTensor `protobuf:"bytes,1,opt,name=dense_tensor,json=denseTensor,oneof"` +} + +type TensorRepresentation_VarlenSparseTensor struct { + VarlenSparseTensor *TensorRepresentation_VarLenSparseTensor `protobuf:"bytes,2,opt,name=varlen_sparse_tensor,json=varlenSparseTensor,oneof"` +} + +type TensorRepresentation_SparseTensor_ struct { + SparseTensor *TensorRepresentation_SparseTensor `protobuf:"bytes,3,opt,name=sparse_tensor,json=sparseTensor,oneof"` +} + +func (*TensorRepresentation_DenseTensor_) isTensorRepresentation_Kind() {} + +func (*TensorRepresentation_VarlenSparseTensor) isTensorRepresentation_Kind() {} + +func (*TensorRepresentation_SparseTensor_) isTensorRepresentation_Kind() {} + +// A TensorRepresentationGroup is a collection of TensorRepresentations with +// names. These names may serve as identifiers when converting the dataset +// to a collection of Tensors or tf.CompositeTensors. +// For example, given the following group: +// { +// key: "dense_tensor" +// tensor_representation { +// dense_tensor { +// column_name: "univalent_feature" +// shape { +// dim { +// size: 1 +// } +// } +// default_value { +// float_value: 0 +// } +// } +// } +// } +// { +// key: "varlen_sparse_tensor" +// tensor_representation { +// varlen_sparse_tensor { +// column_name: "multivalent_feature" +// } +// } +// } +// +// Then the schema is expected to have feature "univalent_feature" and +// "multivalent_feature", and when a batch of data is converted to Tensors using +// this TensorRepresentationGroup, the result may be the following dict: +// { +// "dense_tensor": tf.Tensor(...), +// "varlen_sparse_tensor": tf.SparseTensor(...), +// } +type TensorRepresentationGroup struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TensorRepresentation map[string]*TensorRepresentation `protobuf:"bytes,1,rep,name=tensor_representation,json=tensorRepresentation" json:"tensor_representation,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` +} + +func (x *TensorRepresentationGroup) Reset() { + *x = TensorRepresentationGroup{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TensorRepresentationGroup) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TensorRepresentationGroup) ProtoMessage() {} + +func (x *TensorRepresentationGroup) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[26] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TensorRepresentationGroup.ProtoReflect.Descriptor instead. +func (*TensorRepresentationGroup) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{26} +} + +func (x *TensorRepresentationGroup) GetTensorRepresentation() map[string]*TensorRepresentation { + if x != nil { + return x.TensorRepresentation + } + return nil +} + +// An axis in a multi-dimensional feature representation. +type FixedShape_Dim struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Size *int64 `protobuf:"varint,1,opt,name=size" json:"size,omitempty"` + // Optional name of the tensor dimension. + Name *string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"` +} + +func (x *FixedShape_Dim) Reset() { + *x = FixedShape_Dim{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FixedShape_Dim) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FixedShape_Dim) ProtoMessage() {} + +func (x *FixedShape_Dim) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[28] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FixedShape_Dim.ProtoReflect.Descriptor instead. +func (*FixedShape_Dim) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{5, 0} +} + +func (x *FixedShape_Dim) GetSize() int64 { + if x != nil && x.Size != nil { + return *x.Size + } + return 0 +} + +func (x *FixedShape_Dim) GetName() string { + if x != nil && x.Name != nil { + return *x.Name + } + return "" +} + +type SparseFeature_IndexFeature struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Name of the index-feature. This should be a reference to an existing + // feature in the schema. + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` +} + +func (x *SparseFeature_IndexFeature) Reset() { + *x = SparseFeature_IndexFeature{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SparseFeature_IndexFeature) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SparseFeature_IndexFeature) ProtoMessage() {} + +func (x *SparseFeature_IndexFeature) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[29] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SparseFeature_IndexFeature.ProtoReflect.Descriptor instead. +func (*SparseFeature_IndexFeature) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{8, 0} +} + +func (x *SparseFeature_IndexFeature) GetName() string { + if x != nil && x.Name != nil { + return *x.Name + } + return "" +} + +type SparseFeature_ValueFeature struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Name of the value-feature. This should be a reference to an existing + // feature in the schema. + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` +} + +func (x *SparseFeature_ValueFeature) Reset() { + *x = SparseFeature_ValueFeature{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SparseFeature_ValueFeature) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SparseFeature_ValueFeature) ProtoMessage() {} + +func (x *SparseFeature_ValueFeature) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[30] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SparseFeature_ValueFeature.ProtoReflect.Descriptor instead. +func (*SparseFeature_ValueFeature) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{8, 1} +} + +func (x *SparseFeature_ValueFeature) GetName() string { + if x != nil && x.Name != nil { + return *x.Name + } + return "" +} + +type TensorRepresentation_DefaultValue struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Kind: + // *TensorRepresentation_DefaultValue_FloatValue + // *TensorRepresentation_DefaultValue_IntValue + // *TensorRepresentation_DefaultValue_BytesValue + // *TensorRepresentation_DefaultValue_UintValue + Kind isTensorRepresentation_DefaultValue_Kind `protobuf_oneof:"kind"` +} + +func (x *TensorRepresentation_DefaultValue) Reset() { + *x = TensorRepresentation_DefaultValue{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TensorRepresentation_DefaultValue) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TensorRepresentation_DefaultValue) ProtoMessage() {} + +func (x *TensorRepresentation_DefaultValue) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[31] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TensorRepresentation_DefaultValue.ProtoReflect.Descriptor instead. +func (*TensorRepresentation_DefaultValue) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{25, 0} +} + +func (m *TensorRepresentation_DefaultValue) GetKind() isTensorRepresentation_DefaultValue_Kind { + if m != nil { + return m.Kind + } + return nil +} + +func (x *TensorRepresentation_DefaultValue) GetFloatValue() float64 { + if x, ok := x.GetKind().(*TensorRepresentation_DefaultValue_FloatValue); ok { + return x.FloatValue + } + return 0 +} + +func (x *TensorRepresentation_DefaultValue) GetIntValue() int64 { + if x, ok := x.GetKind().(*TensorRepresentation_DefaultValue_IntValue); ok { + return x.IntValue + } + return 0 +} + +func (x *TensorRepresentation_DefaultValue) GetBytesValue() []byte { + if x, ok := x.GetKind().(*TensorRepresentation_DefaultValue_BytesValue); ok { + return x.BytesValue + } + return nil +} + +func (x *TensorRepresentation_DefaultValue) GetUintValue() uint64 { + if x, ok := x.GetKind().(*TensorRepresentation_DefaultValue_UintValue); ok { + return x.UintValue + } + return 0 +} + +type isTensorRepresentation_DefaultValue_Kind interface { + isTensorRepresentation_DefaultValue_Kind() +} + +type TensorRepresentation_DefaultValue_FloatValue struct { + FloatValue float64 `protobuf:"fixed64,1,opt,name=float_value,json=floatValue,oneof"` +} + +type TensorRepresentation_DefaultValue_IntValue struct { + // Note that the data column might be of a shorter integral type. It's the + // user's responsitiblity to make sure the default value fits that type. + IntValue int64 `protobuf:"varint,2,opt,name=int_value,json=intValue,oneof"` +} + +type TensorRepresentation_DefaultValue_BytesValue struct { + BytesValue []byte `protobuf:"bytes,3,opt,name=bytes_value,json=bytesValue,oneof"` +} + +type TensorRepresentation_DefaultValue_UintValue struct { + // uint_value should only be used if the default value can't fit in a + // int64 (`int_value`). + UintValue uint64 `protobuf:"varint,4,opt,name=uint_value,json=uintValue,oneof"` +} + +func (*TensorRepresentation_DefaultValue_FloatValue) isTensorRepresentation_DefaultValue_Kind() {} + +func (*TensorRepresentation_DefaultValue_IntValue) isTensorRepresentation_DefaultValue_Kind() {} + +func (*TensorRepresentation_DefaultValue_BytesValue) isTensorRepresentation_DefaultValue_Kind() {} + +func (*TensorRepresentation_DefaultValue_UintValue) isTensorRepresentation_DefaultValue_Kind() {} + +// A tf.Tensor +type TensorRepresentation_DenseTensor struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Identifies the column in the dataset that provides the values of this + // Tensor. + ColumnName *string `protobuf:"bytes,1,opt,name=column_name,json=columnName" json:"column_name,omitempty"` + // The shape of each row of the data (i.e. does not include the batch + // dimension) + Shape *FixedShape `protobuf:"bytes,2,opt,name=shape" json:"shape,omitempty"` + // If this column is missing values in a row, the default_value will be + // used to fill that row. + DefaultValue *TensorRepresentation_DefaultValue `protobuf:"bytes,3,opt,name=default_value,json=defaultValue" json:"default_value,omitempty"` +} + +func (x *TensorRepresentation_DenseTensor) Reset() { + *x = TensorRepresentation_DenseTensor{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TensorRepresentation_DenseTensor) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TensorRepresentation_DenseTensor) ProtoMessage() {} + +func (x *TensorRepresentation_DenseTensor) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[32] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TensorRepresentation_DenseTensor.ProtoReflect.Descriptor instead. +func (*TensorRepresentation_DenseTensor) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{25, 1} +} + +func (x *TensorRepresentation_DenseTensor) GetColumnName() string { + if x != nil && x.ColumnName != nil { + return *x.ColumnName + } + return "" +} + +func (x *TensorRepresentation_DenseTensor) GetShape() *FixedShape { + if x != nil { + return x.Shape + } + return nil +} + +func (x *TensorRepresentation_DenseTensor) GetDefaultValue() *TensorRepresentation_DefaultValue { + if x != nil { + return x.DefaultValue + } + return nil +} + +// A ragged tf.SparseTensor that models nested lists. +type TensorRepresentation_VarLenSparseTensor struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Identifies the column in the dataset that should be converted to the + // VarLenSparseTensor. + ColumnName *string `protobuf:"bytes,1,opt,name=column_name,json=columnName" json:"column_name,omitempty"` +} + +func (x *TensorRepresentation_VarLenSparseTensor) Reset() { + *x = TensorRepresentation_VarLenSparseTensor{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TensorRepresentation_VarLenSparseTensor) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TensorRepresentation_VarLenSparseTensor) ProtoMessage() {} + +func (x *TensorRepresentation_VarLenSparseTensor) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[33] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TensorRepresentation_VarLenSparseTensor.ProtoReflect.Descriptor instead. +func (*TensorRepresentation_VarLenSparseTensor) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{25, 2} +} + +func (x *TensorRepresentation_VarLenSparseTensor) GetColumnName() string { + if x != nil && x.ColumnName != nil { + return *x.ColumnName + } + return "" +} + +// A tf.SparseTensor whose indices and values come from separate data columns. +// This will replace Schema.sparse_feature eventually. +// The index columns must be of INT type, and all the columns must co-occur +// and have the same valency at the same row. +type TensorRepresentation_SparseTensor struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The dense shape of the resulting SparseTensor (does not include the batch + // dimension). + DenseShape *FixedShape `protobuf:"bytes,1,opt,name=dense_shape,json=denseShape" json:"dense_shape,omitempty"` + // The columns constitute the coordinates of the values. + // indices_column[i][j] contains the coordinate of the i-th dimension of the + // j-th value. + IndexColumnNames []string `protobuf:"bytes,2,rep,name=index_column_names,json=indexColumnNames" json:"index_column_names,omitempty"` + // The column that contains the values. + ValueColumnName *string `protobuf:"bytes,3,opt,name=value_column_name,json=valueColumnName" json:"value_column_name,omitempty"` +} + +func (x *TensorRepresentation_SparseTensor) Reset() { + *x = TensorRepresentation_SparseTensor{} + if protoimpl.UnsafeEnabled { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TensorRepresentation_SparseTensor) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TensorRepresentation_SparseTensor) ProtoMessage() {} + +func (x *TensorRepresentation_SparseTensor) ProtoReflect() protoreflect.Message { + mi := &file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[34] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TensorRepresentation_SparseTensor.ProtoReflect.Descriptor instead. +func (*TensorRepresentation_SparseTensor) Descriptor() ([]byte, []int) { + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP(), []int{25, 3} +} + +func (x *TensorRepresentation_SparseTensor) GetDenseShape() *FixedShape { + if x != nil { + return x.DenseShape + } + return nil +} + +func (x *TensorRepresentation_SparseTensor) GetIndexColumnNames() []string { + if x != nil { + return x.IndexColumnNames + } + return nil +} + +func (x *TensorRepresentation_SparseTensor) GetValueColumnName() string { + if x != nil && x.ValueColumnName != nil { + return *x.ValueColumnName + } + return "" +} + +var File_tensorflow_metadata_proto_v0_schema_proto protoreflect.FileDescriptor + +var file_tensorflow_metadata_proto_v0_schema_proto_rawDesc = []byte{ + 0x0a, 0x29, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x30, 0x2f, 0x73, + 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x16, 0x74, 0x65, 0x6e, + 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x2e, 0x76, 0x30, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x27, + 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x30, 0x2f, 0x70, 0x61, 0x74, + 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x8c, 0x07, 0x0a, 0x06, 0x53, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x12, 0x39, 0x0a, 0x07, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, + 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x46, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x52, 0x07, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x4c, 0x0a, + 0x0e, 0x73, 0x70, 0x61, 0x72, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, + 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, + 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x53, + 0x70, 0x61, 0x72, 0x73, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x0d, 0x73, 0x70, + 0x61, 0x72, 0x73, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x52, 0x0a, 0x10, 0x77, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, + 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, + 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x57, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x65, 0x64, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x0f, + 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x65, 0x64, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, + 0x49, 0x0a, 0x0d, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, + 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, + 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x0c, 0x73, 0x74, + 0x72, 0x69, 0x6e, 0x67, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x46, 0x0a, 0x0c, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x23, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x44, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x0b, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x44, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x12, 0x40, 0x0a, 0x0a, 0x69, 0x6e, 0x74, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, + 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, + 0x49, 0x6e, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x09, 0x69, 0x6e, 0x74, 0x44, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x2f, 0x0a, 0x13, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, + 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x12, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, + 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x42, 0x0a, 0x0a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6e, 0x73, + 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, + 0x76, 0x30, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x61, + 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x5b, 0x0a, 0x13, 0x64, 0x61, 0x74, + 0x61, 0x73, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, + 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, + 0x44, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, + 0x74, 0x73, 0x52, 0x12, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x74, + 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x7d, 0x0a, 0x1b, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, + 0x5f, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x74, 0x65, + 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x65, 0x6e, 0x73, + 0x6f, 0x72, 0x52, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x47, 0x72, 0x6f, 0x75, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x19, 0x74, 0x65, 0x6e, 0x73, + 0x6f, 0x72, 0x52, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x47, 0x72, 0x6f, 0x75, 0x70, 0x1a, 0x7f, 0x0a, 0x1e, 0x54, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x52, + 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x47, 0x72, 0x6f, + 0x75, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x47, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, + 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, + 0x30, 0x2e, 0x54, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x52, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb9, 0x0e, 0x0a, 0x07, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, + 0x61, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, + 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x45, 0x0a, 0x08, 0x70, 0x72, + 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x74, + 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x50, 0x72, 0x65, + 0x73, 0x65, 0x6e, 0x63, 0x65, 0x48, 0x00, 0x52, 0x08, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, + 0x65, 0x12, 0x5b, 0x0a, 0x0e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x70, 0x72, 0x65, 0x73, 0x65, + 0x6e, 0x63, 0x65, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x74, 0x65, 0x6e, 0x73, + 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, + 0x76, 0x30, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, + 0x63, 0x65, 0x57, 0x69, 0x74, 0x68, 0x69, 0x6e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x48, 0x00, 0x52, + 0x0d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x3a, + 0x0a, 0x05, 0x73, 0x68, 0x61, 0x70, 0x65, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, + 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x46, 0x69, 0x78, 0x65, 0x64, 0x53, 0x68, 0x61, 0x70, + 0x65, 0x48, 0x01, 0x52, 0x05, 0x73, 0x68, 0x61, 0x70, 0x65, 0x12, 0x45, 0x0a, 0x0b, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x22, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x48, 0x01, 0x52, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x37, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x23, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x06, 0x64, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x06, 0x64, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x42, 0x0a, 0x0a, 0x69, 0x6e, 0x74, 0x5f, 0x64, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, + 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, + 0x30, 0x2e, 0x49, 0x6e, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x48, 0x02, 0x52, 0x09, 0x69, + 0x6e, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x48, 0x0a, 0x0c, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, + 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x44, 0x6f, 0x6d, + 0x61, 0x69, 0x6e, 0x48, 0x02, 0x52, 0x0b, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x44, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x12, 0x4b, 0x0a, 0x0d, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x6f, 0x6d, + 0x61, 0x69, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x74, 0x65, 0x6e, 0x73, + 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, + 0x76, 0x30, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x48, + 0x02, 0x52, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, + 0x45, 0x0a, 0x0b, 0x62, 0x6f, 0x6f, 0x6c, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x0d, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, + 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x42, 0x6f, + 0x6f, 0x6c, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x48, 0x02, 0x52, 0x0a, 0x62, 0x6f, 0x6f, 0x6c, + 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x4b, 0x0a, 0x0d, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, + 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, + 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x44, 0x6f, 0x6d, + 0x61, 0x69, 0x6e, 0x48, 0x02, 0x52, 0x0c, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x44, 0x6f, 0x6d, + 0x61, 0x69, 0x6e, 0x12, 0x67, 0x0a, 0x17, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x61, 0x6c, 0x5f, 0x6c, + 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x18, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, + 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x4e, 0x61, + 0x74, 0x75, 0x72, 0x61, 0x6c, 0x4c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x44, 0x6f, 0x6d, + 0x61, 0x69, 0x6e, 0x48, 0x02, 0x52, 0x15, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x61, 0x6c, 0x4c, 0x61, + 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x48, 0x0a, 0x0c, + 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x19, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x49, 0x6d, 0x61, 0x67, + 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x48, 0x02, 0x52, 0x0b, 0x69, 0x6d, 0x61, 0x67, 0x65, + 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x42, 0x0a, 0x0a, 0x6d, 0x69, 0x64, 0x5f, 0x64, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x74, 0x65, 0x6e, + 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x2e, 0x76, 0x30, 0x2e, 0x4d, 0x49, 0x44, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x48, 0x02, 0x52, + 0x09, 0x6d, 0x69, 0x64, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x42, 0x0a, 0x0a, 0x75, 0x72, + 0x6c, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, + 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x55, 0x52, 0x4c, 0x44, 0x6f, 0x6d, 0x61, 0x69, + 0x6e, 0x48, 0x02, 0x52, 0x09, 0x75, 0x72, 0x6c, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x45, + 0x0a, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x1c, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, + 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x54, 0x69, 0x6d, + 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x48, 0x02, 0x52, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x44, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x56, 0x0a, 0x12, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6f, 0x66, + 0x5f, 0x64, 0x61, 0x79, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x1e, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x27, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x4f, + 0x66, 0x44, 0x61, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x48, 0x02, 0x52, 0x0f, 0x74, 0x69, + 0x6d, 0x65, 0x4f, 0x66, 0x44, 0x61, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x6a, 0x0a, + 0x18, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, + 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x2f, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, + 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73, + 0x52, 0x17, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, + 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x42, 0x0a, 0x0a, 0x61, 0x6e, 0x6e, + 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, + 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x0a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x52, 0x0a, + 0x0f, 0x73, 0x6b, 0x65, 0x77, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x61, 0x74, 0x6f, 0x72, + 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, + 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x61, 0x74, 0x6f, + 0x72, 0x52, 0x0e, 0x73, 0x6b, 0x65, 0x77, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x61, 0x74, 0x6f, + 0x72, 0x12, 0x54, 0x0a, 0x10, 0x64, 0x72, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, + 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x74, 0x65, + 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x6d, 0x70, + 0x61, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x0f, 0x64, 0x72, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6d, + 0x70, 0x61, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x6e, 0x5f, 0x65, 0x6e, + 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x14, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x0d, 0x69, 0x6e, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x2c, + 0x0a, 0x12, 0x6e, 0x6f, 0x74, 0x5f, 0x69, 0x6e, 0x5f, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, + 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x13, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x6e, 0x6f, 0x74, 0x49, + 0x6e, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x4f, 0x0a, 0x0f, + 0x6c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, + 0x16, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, + 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x4c, + 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x0e, 0x6c, + 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x67, 0x65, 0x42, 0x16, 0x0a, + 0x14, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, + 0x61, 0x69, 0x6e, 0x74, 0x73, 0x42, 0x0c, 0x0a, 0x0a, 0x73, 0x68, 0x61, 0x70, 0x65, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x42, 0x0d, 0x0a, 0x0b, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x6e, + 0x66, 0x6f, 0x22, 0x75, 0x0a, 0x0a, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x74, + 0x61, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x3b, 0x0a, 0x0e, + 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x0d, 0x65, 0x78, 0x74, 0x72, + 0x61, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x84, 0x01, 0x0a, 0x16, 0x4e, 0x75, + 0x6d, 0x65, 0x72, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, + 0x61, 0x74, 0x6f, 0x72, 0x12, 0x34, 0x0a, 0x16, 0x6d, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x01, 0x52, 0x14, 0x6d, 0x69, 0x6e, 0x46, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x34, 0x0a, 0x16, 0x6d, 0x61, + 0x78, 0x5f, 0x66, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, + 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x14, 0x6d, 0x61, 0x78, 0x46, + 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, + 0x22, 0xac, 0x02, 0x0a, 0x12, 0x44, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x73, + 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x71, 0x0a, 0x1d, 0x6e, 0x75, 0x6d, 0x5f, 0x65, + 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x5f, 0x64, 0x72, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, + 0x6d, 0x70, 0x61, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, + 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x1a, + 0x6e, 0x75, 0x6d, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x44, 0x72, 0x69, 0x66, 0x74, + 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x75, 0x0a, 0x1f, 0x6e, 0x75, + 0x6d, 0x5f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, + 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x4e, 0x75, 0x6d, + 0x65, 0x72, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x61, + 0x74, 0x6f, 0x72, 0x52, 0x1c, 0x6e, 0x75, 0x6d, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x61, 0x74, 0x6f, + 0x72, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x69, 0x6e, 0x5f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, + 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x6d, + 0x69, 0x6e, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, + 0x75, 0x0a, 0x0a, 0x46, 0x69, 0x78, 0x65, 0x64, 0x53, 0x68, 0x61, 0x70, 0x65, 0x12, 0x38, 0x0a, + 0x03, 0x64, 0x69, 0x6d, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x74, 0x65, 0x6e, + 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x2e, 0x76, 0x30, 0x2e, 0x46, 0x69, 0x78, 0x65, 0x64, 0x53, 0x68, 0x61, 0x70, 0x65, 0x2e, 0x44, + 0x69, 0x6d, 0x52, 0x03, 0x64, 0x69, 0x6d, 0x1a, 0x2d, 0x0a, 0x03, 0x44, 0x69, 0x6d, 0x12, 0x12, + 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, + 0x7a, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x30, 0x0a, 0x0a, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x03, 0x6d, 0x69, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x61, 0x78, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x03, 0x6d, 0x61, 0x78, 0x22, 0xf3, 0x01, 0x0a, 0x0f, 0x57, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x65, 0x64, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x36, 0x0a, 0x07, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x52, + 0x07, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x43, 0x0a, 0x0e, 0x77, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x52, 0x0d, + 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x4f, 0x0a, + 0x0f, 0x6c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x67, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, + 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, + 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x0e, + 0x6c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x67, 0x65, 0x22, 0x80, + 0x05, 0x0a, 0x0d, 0x53, 0x70, 0x61, 0x72, 0x73, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, + 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x64, 0x65, + 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x4f, 0x0a, 0x0f, 0x6c, 0x69, 0x66, 0x65, + 0x63, 0x79, 0x63, 0x6c, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x26, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x4c, 0x69, 0x66, 0x65, 0x63, + 0x79, 0x63, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x0e, 0x6c, 0x69, 0x66, 0x65, 0x63, + 0x79, 0x63, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x67, 0x65, 0x12, 0x47, 0x0a, 0x08, 0x70, 0x72, 0x65, + 0x73, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x74, 0x65, + 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x50, 0x72, 0x65, 0x73, + 0x65, 0x6e, 0x63, 0x65, 0x42, 0x02, 0x18, 0x01, 0x52, 0x08, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, + 0x63, 0x65, 0x12, 0x43, 0x0a, 0x0b, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x73, 0x68, 0x61, 0x70, + 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, + 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, + 0x2e, 0x46, 0x69, 0x78, 0x65, 0x64, 0x53, 0x68, 0x61, 0x70, 0x65, 0x52, 0x0a, 0x64, 0x65, 0x6e, + 0x73, 0x65, 0x53, 0x68, 0x61, 0x70, 0x65, 0x12, 0x57, 0x0a, 0x0d, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, + 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x53, 0x70, 0x61, 0x72, 0x73, 0x65, 0x46, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x2e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x52, 0x0c, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x73, 0x5f, 0x73, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73, 0x53, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x57, 0x0a, + 0x0d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, + 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x53, 0x70, + 0x61, 0x72, 0x73, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x2e, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x0c, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x46, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, + 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x46, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x42, 0x02, 0x18, 0x01, 0x52, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x1a, 0x22, 0x0a, 0x0c, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x1a, 0x22, 0x0a, 0x0c, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x4a, 0x04, 0x08, 0x0b, 0x10, + 0x0c, 0x22, 0x44, 0x0a, 0x17, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, + 0x6e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x29, 0x0a, 0x0f, + 0x6d, 0x69, 0x6e, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x73, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x01, 0x3a, 0x01, 0x31, 0x52, 0x0d, 0x6d, 0x69, 0x6e, 0x44, 0x6f, 0x6d, + 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x73, 0x73, 0x22, 0x6a, 0x0a, 0x09, 0x49, 0x6e, 0x74, 0x44, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x69, 0x6e, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6d, 0x69, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x61, + 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6d, 0x61, 0x78, 0x12, 0x25, 0x0a, 0x0e, + 0x69, 0x73, 0x5f, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x63, 0x61, 0x6c, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x69, 0x73, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, + 0x63, 0x61, 0x6c, 0x22, 0x45, 0x0a, 0x0b, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x44, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x69, 0x6e, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x02, 0x52, 0x03, 0x6d, 0x69, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x61, 0x78, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x02, 0x52, 0x03, 0x6d, 0x61, 0x78, 0x22, 0x97, 0x01, 0x0a, 0x0c, 0x53, + 0x74, 0x72, 0x75, 0x63, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x39, 0x0a, 0x07, 0x66, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x74, + 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x07, 0x66, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x4c, 0x0a, 0x0e, 0x73, 0x70, 0x61, 0x72, 0x73, 0x65, + 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, + 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x53, 0x70, 0x61, 0x72, 0x73, 0x65, 0x46, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x0d, 0x73, 0x70, 0x61, 0x72, 0x73, 0x65, 0x46, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x22, 0x38, 0x0a, 0x0c, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x44, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x60, + 0x0a, 0x0a, 0x42, 0x6f, 0x6f, 0x6c, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x72, 0x75, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x72, 0x75, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, + 0x1f, 0x0a, 0x0b, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x22, 0x17, 0x0a, 0x15, 0x4e, 0x61, 0x74, 0x75, 0x72, 0x61, 0x6c, 0x4c, 0x61, 0x6e, 0x67, 0x75, + 0x61, 0x67, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x22, 0x0d, 0x0a, 0x0b, 0x49, 0x6d, 0x61, + 0x67, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x22, 0x0b, 0x0a, 0x09, 0x4d, 0x49, 0x44, 0x44, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x22, 0x0b, 0x0a, 0x09, 0x55, 0x52, 0x4c, 0x44, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x22, 0xab, 0x02, 0x0a, 0x0a, 0x54, 0x69, 0x6d, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, + 0x6e, 0x12, 0x25, 0x0a, 0x0d, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x6f, 0x72, 0x6d, + 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, 0x73, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x5d, 0x0a, 0x0e, 0x69, 0x6e, 0x74, 0x65, + 0x67, 0x65, 0x72, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x34, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x44, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, + 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, + 0x72, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x22, 0x8c, 0x01, 0x0a, 0x11, 0x49, 0x6e, 0x74, 0x65, + 0x67, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x12, 0x0a, + 0x0e, 0x46, 0x4f, 0x52, 0x4d, 0x41, 0x54, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, + 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x49, 0x58, 0x5f, 0x44, 0x41, 0x59, 0x53, 0x10, 0x05, + 0x12, 0x10, 0x0a, 0x0c, 0x55, 0x4e, 0x49, 0x58, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x53, + 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x55, 0x4e, 0x49, 0x58, 0x5f, 0x4d, 0x49, 0x4c, 0x4c, 0x49, + 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x53, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x55, 0x4e, 0x49, + 0x58, 0x5f, 0x4d, 0x49, 0x43, 0x52, 0x4f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x53, 0x10, 0x03, + 0x12, 0x14, 0x0a, 0x10, 0x55, 0x4e, 0x49, 0x58, 0x5f, 0x4e, 0x41, 0x4e, 0x4f, 0x53, 0x45, 0x43, + 0x4f, 0x4e, 0x44, 0x53, 0x10, 0x04, 0x42, 0x08, 0x0a, 0x06, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, + 0x22, 0xee, 0x01, 0x0a, 0x0f, 0x54, 0x69, 0x6d, 0x65, 0x4f, 0x66, 0x44, 0x61, 0x79, 0x44, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x25, 0x0a, 0x0d, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x66, + 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, 0x73, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x67, 0x0a, 0x0e, 0x69, + 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x3e, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, + 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x54, 0x69, 0x6d, + 0x65, 0x4f, 0x66, 0x44, 0x61, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x49, 0x6e, 0x74, + 0x65, 0x67, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4f, 0x66, 0x44, 0x61, 0x79, 0x46, 0x6f, 0x72, + 0x6d, 0x61, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x46, 0x6f, + 0x72, 0x6d, 0x61, 0x74, 0x22, 0x41, 0x0a, 0x16, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x54, + 0x69, 0x6d, 0x65, 0x4f, 0x66, 0x44, 0x61, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x12, + 0x0a, 0x0e, 0x46, 0x4f, 0x52, 0x4d, 0x41, 0x54, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, + 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x50, 0x41, 0x43, 0x4b, 0x45, 0x44, 0x5f, 0x36, 0x34, 0x5f, + 0x4e, 0x41, 0x4e, 0x4f, 0x53, 0x10, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x66, 0x6f, 0x72, 0x6d, 0x61, + 0x74, 0x22, 0x51, 0x0a, 0x0f, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x50, 0x72, 0x65, 0x73, + 0x65, 0x6e, 0x63, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x46, + 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x38, 0x0a, 0x1a, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x50, + 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x57, 0x69, 0x74, 0x68, 0x69, 0x6e, 0x47, 0x72, 0x6f, + 0x75, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x22, 0x2c, + 0x0a, 0x0c, 0x49, 0x6e, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x79, 0x4e, 0x6f, 0x72, 0x6d, 0x12, 0x1c, + 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x01, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x22, 0x5e, 0x0a, 0x11, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x61, 0x74, 0x6f, + 0x72, 0x12, 0x49, 0x0a, 0x0d, 0x69, 0x6e, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x79, 0x5f, 0x6e, 0x6f, + 0x72, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, + 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, + 0x30, 0x2e, 0x49, 0x6e, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x79, 0x4e, 0x6f, 0x72, 0x6d, 0x52, 0x0c, + 0x69, 0x6e, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x79, 0x4e, 0x6f, 0x72, 0x6d, 0x22, 0xa5, 0x07, 0x0a, + 0x14, 0x54, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x52, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x5d, 0x0a, 0x0c, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x74, + 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x74, 0x65, + 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x54, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x52, 0x65, 0x70, 0x72, 0x65, + 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x6e, 0x73, 0x65, 0x54, + 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x48, 0x00, 0x52, 0x0b, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x54, 0x65, + 0x6e, 0x73, 0x6f, 0x72, 0x12, 0x73, 0x0a, 0x14, 0x76, 0x61, 0x72, 0x6c, 0x65, 0x6e, 0x5f, 0x73, + 0x70, 0x61, 0x72, 0x73, 0x65, 0x5f, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x3f, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x54, 0x65, 0x6e, 0x73, + 0x6f, 0x72, 0x52, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x2e, 0x56, 0x61, 0x72, 0x4c, 0x65, 0x6e, 0x53, 0x70, 0x61, 0x72, 0x73, 0x65, 0x54, 0x65, 0x6e, + 0x73, 0x6f, 0x72, 0x48, 0x00, 0x52, 0x12, 0x76, 0x61, 0x72, 0x6c, 0x65, 0x6e, 0x53, 0x70, 0x61, + 0x72, 0x73, 0x65, 0x54, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x12, 0x60, 0x0a, 0x0d, 0x73, 0x70, 0x61, + 0x72, 0x73, 0x65, 0x5f, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x39, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x54, 0x65, 0x6e, 0x73, 0x6f, 0x72, + 0x52, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x53, + 0x70, 0x61, 0x72, 0x73, 0x65, 0x54, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x48, 0x00, 0x52, 0x0c, 0x73, + 0x70, 0x61, 0x72, 0x73, 0x65, 0x54, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x1a, 0x9c, 0x01, 0x0a, 0x0c, + 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x21, 0x0a, 0x0b, + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x01, 0x48, 0x00, 0x52, 0x0a, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, + 0x1d, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x48, 0x00, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x21, + 0x0a, 0x0b, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x12, 0x1f, 0x0a, 0x0a, 0x75, 0x69, 0x6e, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x09, 0x75, 0x69, 0x6e, 0x74, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x1a, 0xc8, 0x01, 0x0a, 0x0b, 0x44, + 0x65, 0x6e, 0x73, 0x65, 0x54, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, + 0x6c, 0x75, 0x6d, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x38, 0x0a, 0x05, 0x73, + 0x68, 0x61, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6e, + 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x2e, 0x76, 0x30, 0x2e, 0x46, 0x69, 0x78, 0x65, 0x64, 0x53, 0x68, 0x61, 0x70, 0x65, 0x52, 0x05, + 0x73, 0x68, 0x61, 0x70, 0x65, 0x12, 0x5e, 0x0a, 0x0d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, + 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x74, + 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x54, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x52, 0x65, 0x70, 0x72, + 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x66, 0x61, 0x75, + 0x6c, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x35, 0x0a, 0x12, 0x56, 0x61, 0x72, 0x4c, 0x65, 0x6e, 0x53, + 0x70, 0x61, 0x72, 0x73, 0x65, 0x54, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x63, + 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x1a, 0xad, 0x01, 0x0a, + 0x0c, 0x53, 0x70, 0x61, 0x72, 0x73, 0x65, 0x54, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x12, 0x43, 0x0a, + 0x0b, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x73, 0x68, 0x61, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x46, 0x69, 0x78, 0x65, + 0x64, 0x53, 0x68, 0x61, 0x70, 0x65, 0x52, 0x0a, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x53, 0x68, 0x61, + 0x70, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x63, 0x6f, 0x6c, 0x75, + 0x6d, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, + 0x69, 0x6e, 0x64, 0x65, 0x78, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x73, + 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x42, 0x06, 0x0a, 0x04, + 0x6b, 0x69, 0x6e, 0x64, 0x22, 0x95, 0x02, 0x0a, 0x19, 0x54, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x52, + 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x47, 0x72, 0x6f, + 0x75, 0x70, 0x12, 0x80, 0x01, 0x0a, 0x15, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x5f, 0x72, 0x65, + 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x4b, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x54, 0x65, 0x6e, 0x73, + 0x6f, 0x72, 0x52, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x47, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x54, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x52, 0x65, 0x70, 0x72, + 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x14, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x52, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x75, 0x0a, 0x19, 0x54, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x52, + 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x42, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, + 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x2e, 0x54, 0x65, 0x6e, + 0x73, 0x6f, 0x72, 0x52, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x2a, 0x75, 0x0a, 0x0e, + 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x67, 0x65, 0x12, 0x11, + 0x0a, 0x0d, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, 0x10, + 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x4c, 0x41, 0x4e, 0x4e, 0x45, 0x44, 0x10, 0x01, 0x12, 0x09, + 0x0a, 0x05, 0x41, 0x4c, 0x50, 0x48, 0x41, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x42, 0x45, 0x54, + 0x41, 0x10, 0x03, 0x12, 0x0e, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x44, 0x55, 0x43, 0x54, 0x49, 0x4f, + 0x4e, 0x10, 0x04, 0x12, 0x0e, 0x0a, 0x0a, 0x44, 0x45, 0x50, 0x52, 0x45, 0x43, 0x41, 0x54, 0x45, + 0x44, 0x10, 0x05, 0x12, 0x0e, 0x0a, 0x0a, 0x44, 0x45, 0x42, 0x55, 0x47, 0x5f, 0x4f, 0x4e, 0x4c, + 0x59, 0x10, 0x06, 0x2a, 0x4a, 0x0a, 0x0b, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, + 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x42, 0x59, 0x54, 0x45, 0x53, 0x10, 0x01, 0x12, + 0x07, 0x0a, 0x03, 0x49, 0x4e, 0x54, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x46, 0x4c, 0x4f, 0x41, + 0x54, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x52, 0x55, 0x43, 0x54, 0x10, 0x04, 0x42, + 0x68, 0x0a, 0x1a, 0x6f, 0x72, 0x67, 0x2e, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, + 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x76, 0x30, 0x50, 0x01, 0x5a, + 0x45, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x66, 0x65, 0x61, 0x73, + 0x74, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x73, 0x64, 0x6b, 0x2f, + 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x74, 0x65, 0x6e, 0x73, 0x6f, 0x72, + 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x30, 0xf8, 0x01, 0x01, +} + +var ( + file_tensorflow_metadata_proto_v0_schema_proto_rawDescOnce sync.Once + file_tensorflow_metadata_proto_v0_schema_proto_rawDescData = file_tensorflow_metadata_proto_v0_schema_proto_rawDesc +) + +func file_tensorflow_metadata_proto_v0_schema_proto_rawDescGZIP() []byte { + file_tensorflow_metadata_proto_v0_schema_proto_rawDescOnce.Do(func() { + file_tensorflow_metadata_proto_v0_schema_proto_rawDescData = protoimpl.X.CompressGZIP(file_tensorflow_metadata_proto_v0_schema_proto_rawDescData) + }) + return file_tensorflow_metadata_proto_v0_schema_proto_rawDescData +} + +var file_tensorflow_metadata_proto_v0_schema_proto_enumTypes = make([]protoimpl.EnumInfo, 4) +var file_tensorflow_metadata_proto_v0_schema_proto_msgTypes = make([]protoimpl.MessageInfo, 36) +var file_tensorflow_metadata_proto_v0_schema_proto_goTypes = []interface{}{ + (LifecycleStage)(0), // 0: tensorflow.metadata.v0.LifecycleStage + (FeatureType)(0), // 1: tensorflow.metadata.v0.FeatureType + (TimeDomain_IntegerTimeFormat)(0), // 2: tensorflow.metadata.v0.TimeDomain.IntegerTimeFormat + (TimeOfDayDomain_IntegerTimeOfDayFormat)(0), // 3: tensorflow.metadata.v0.TimeOfDayDomain.IntegerTimeOfDayFormat + (*Schema)(nil), // 4: tensorflow.metadata.v0.Schema + (*Feature)(nil), // 5: tensorflow.metadata.v0.Feature + (*Annotation)(nil), // 6: tensorflow.metadata.v0.Annotation + (*NumericValueComparator)(nil), // 7: tensorflow.metadata.v0.NumericValueComparator + (*DatasetConstraints)(nil), // 8: tensorflow.metadata.v0.DatasetConstraints + (*FixedShape)(nil), // 9: tensorflow.metadata.v0.FixedShape + (*ValueCount)(nil), // 10: tensorflow.metadata.v0.ValueCount + (*WeightedFeature)(nil), // 11: tensorflow.metadata.v0.WeightedFeature + (*SparseFeature)(nil), // 12: tensorflow.metadata.v0.SparseFeature + (*DistributionConstraints)(nil), // 13: tensorflow.metadata.v0.DistributionConstraints + (*IntDomain)(nil), // 14: tensorflow.metadata.v0.IntDomain + (*FloatDomain)(nil), // 15: tensorflow.metadata.v0.FloatDomain + (*StructDomain)(nil), // 16: tensorflow.metadata.v0.StructDomain + (*StringDomain)(nil), // 17: tensorflow.metadata.v0.StringDomain + (*BoolDomain)(nil), // 18: tensorflow.metadata.v0.BoolDomain + (*NaturalLanguageDomain)(nil), // 19: tensorflow.metadata.v0.NaturalLanguageDomain + (*ImageDomain)(nil), // 20: tensorflow.metadata.v0.ImageDomain + (*MIDDomain)(nil), // 21: tensorflow.metadata.v0.MIDDomain + (*URLDomain)(nil), // 22: tensorflow.metadata.v0.URLDomain + (*TimeDomain)(nil), // 23: tensorflow.metadata.v0.TimeDomain + (*TimeOfDayDomain)(nil), // 24: tensorflow.metadata.v0.TimeOfDayDomain + (*FeaturePresence)(nil), // 25: tensorflow.metadata.v0.FeaturePresence + (*FeaturePresenceWithinGroup)(nil), // 26: tensorflow.metadata.v0.FeaturePresenceWithinGroup + (*InfinityNorm)(nil), // 27: tensorflow.metadata.v0.InfinityNorm + (*FeatureComparator)(nil), // 28: tensorflow.metadata.v0.FeatureComparator + (*TensorRepresentation)(nil), // 29: tensorflow.metadata.v0.TensorRepresentation + (*TensorRepresentationGroup)(nil), // 30: tensorflow.metadata.v0.TensorRepresentationGroup + nil, // 31: tensorflow.metadata.v0.Schema.TensorRepresentationGroupEntry + (*FixedShape_Dim)(nil), // 32: tensorflow.metadata.v0.FixedShape.Dim + (*SparseFeature_IndexFeature)(nil), // 33: tensorflow.metadata.v0.SparseFeature.IndexFeature + (*SparseFeature_ValueFeature)(nil), // 34: tensorflow.metadata.v0.SparseFeature.ValueFeature + (*TensorRepresentation_DefaultValue)(nil), // 35: tensorflow.metadata.v0.TensorRepresentation.DefaultValue + (*TensorRepresentation_DenseTensor)(nil), // 36: tensorflow.metadata.v0.TensorRepresentation.DenseTensor + (*TensorRepresentation_VarLenSparseTensor)(nil), // 37: tensorflow.metadata.v0.TensorRepresentation.VarLenSparseTensor + (*TensorRepresentation_SparseTensor)(nil), // 38: tensorflow.metadata.v0.TensorRepresentation.SparseTensor + nil, // 39: tensorflow.metadata.v0.TensorRepresentationGroup.TensorRepresentationEntry + (*any.Any)(nil), // 40: google.protobuf.Any + (*Path)(nil), // 41: tensorflow.metadata.v0.Path +} +var file_tensorflow_metadata_proto_v0_schema_proto_depIdxs = []int32{ + 5, // 0: tensorflow.metadata.v0.Schema.feature:type_name -> tensorflow.metadata.v0.Feature + 12, // 1: tensorflow.metadata.v0.Schema.sparse_feature:type_name -> tensorflow.metadata.v0.SparseFeature + 11, // 2: tensorflow.metadata.v0.Schema.weighted_feature:type_name -> tensorflow.metadata.v0.WeightedFeature + 17, // 3: tensorflow.metadata.v0.Schema.string_domain:type_name -> tensorflow.metadata.v0.StringDomain + 15, // 4: tensorflow.metadata.v0.Schema.float_domain:type_name -> tensorflow.metadata.v0.FloatDomain + 14, // 5: tensorflow.metadata.v0.Schema.int_domain:type_name -> tensorflow.metadata.v0.IntDomain + 6, // 6: tensorflow.metadata.v0.Schema.annotation:type_name -> tensorflow.metadata.v0.Annotation + 8, // 7: tensorflow.metadata.v0.Schema.dataset_constraints:type_name -> tensorflow.metadata.v0.DatasetConstraints + 31, // 8: tensorflow.metadata.v0.Schema.tensor_representation_group:type_name -> tensorflow.metadata.v0.Schema.TensorRepresentationGroupEntry + 25, // 9: tensorflow.metadata.v0.Feature.presence:type_name -> tensorflow.metadata.v0.FeaturePresence + 26, // 10: tensorflow.metadata.v0.Feature.group_presence:type_name -> tensorflow.metadata.v0.FeaturePresenceWithinGroup + 9, // 11: tensorflow.metadata.v0.Feature.shape:type_name -> tensorflow.metadata.v0.FixedShape + 10, // 12: tensorflow.metadata.v0.Feature.value_count:type_name -> tensorflow.metadata.v0.ValueCount + 1, // 13: tensorflow.metadata.v0.Feature.type:type_name -> tensorflow.metadata.v0.FeatureType + 14, // 14: tensorflow.metadata.v0.Feature.int_domain:type_name -> tensorflow.metadata.v0.IntDomain + 15, // 15: tensorflow.metadata.v0.Feature.float_domain:type_name -> tensorflow.metadata.v0.FloatDomain + 17, // 16: tensorflow.metadata.v0.Feature.string_domain:type_name -> tensorflow.metadata.v0.StringDomain + 18, // 17: tensorflow.metadata.v0.Feature.bool_domain:type_name -> tensorflow.metadata.v0.BoolDomain + 16, // 18: tensorflow.metadata.v0.Feature.struct_domain:type_name -> tensorflow.metadata.v0.StructDomain + 19, // 19: tensorflow.metadata.v0.Feature.natural_language_domain:type_name -> tensorflow.metadata.v0.NaturalLanguageDomain + 20, // 20: tensorflow.metadata.v0.Feature.image_domain:type_name -> tensorflow.metadata.v0.ImageDomain + 21, // 21: tensorflow.metadata.v0.Feature.mid_domain:type_name -> tensorflow.metadata.v0.MIDDomain + 22, // 22: tensorflow.metadata.v0.Feature.url_domain:type_name -> tensorflow.metadata.v0.URLDomain + 23, // 23: tensorflow.metadata.v0.Feature.time_domain:type_name -> tensorflow.metadata.v0.TimeDomain + 24, // 24: tensorflow.metadata.v0.Feature.time_of_day_domain:type_name -> tensorflow.metadata.v0.TimeOfDayDomain + 13, // 25: tensorflow.metadata.v0.Feature.distribution_constraints:type_name -> tensorflow.metadata.v0.DistributionConstraints + 6, // 26: tensorflow.metadata.v0.Feature.annotation:type_name -> tensorflow.metadata.v0.Annotation + 28, // 27: tensorflow.metadata.v0.Feature.skew_comparator:type_name -> tensorflow.metadata.v0.FeatureComparator + 28, // 28: tensorflow.metadata.v0.Feature.drift_comparator:type_name -> tensorflow.metadata.v0.FeatureComparator + 0, // 29: tensorflow.metadata.v0.Feature.lifecycle_stage:type_name -> tensorflow.metadata.v0.LifecycleStage + 40, // 30: tensorflow.metadata.v0.Annotation.extra_metadata:type_name -> google.protobuf.Any + 7, // 31: tensorflow.metadata.v0.DatasetConstraints.num_examples_drift_comparator:type_name -> tensorflow.metadata.v0.NumericValueComparator + 7, // 32: tensorflow.metadata.v0.DatasetConstraints.num_examples_version_comparator:type_name -> tensorflow.metadata.v0.NumericValueComparator + 32, // 33: tensorflow.metadata.v0.FixedShape.dim:type_name -> tensorflow.metadata.v0.FixedShape.Dim + 41, // 34: tensorflow.metadata.v0.WeightedFeature.feature:type_name -> tensorflow.metadata.v0.Path + 41, // 35: tensorflow.metadata.v0.WeightedFeature.weight_feature:type_name -> tensorflow.metadata.v0.Path + 0, // 36: tensorflow.metadata.v0.WeightedFeature.lifecycle_stage:type_name -> tensorflow.metadata.v0.LifecycleStage + 0, // 37: tensorflow.metadata.v0.SparseFeature.lifecycle_stage:type_name -> tensorflow.metadata.v0.LifecycleStage + 25, // 38: tensorflow.metadata.v0.SparseFeature.presence:type_name -> tensorflow.metadata.v0.FeaturePresence + 9, // 39: tensorflow.metadata.v0.SparseFeature.dense_shape:type_name -> tensorflow.metadata.v0.FixedShape + 33, // 40: tensorflow.metadata.v0.SparseFeature.index_feature:type_name -> tensorflow.metadata.v0.SparseFeature.IndexFeature + 34, // 41: tensorflow.metadata.v0.SparseFeature.value_feature:type_name -> tensorflow.metadata.v0.SparseFeature.ValueFeature + 1, // 42: tensorflow.metadata.v0.SparseFeature.type:type_name -> tensorflow.metadata.v0.FeatureType + 5, // 43: tensorflow.metadata.v0.StructDomain.feature:type_name -> tensorflow.metadata.v0.Feature + 12, // 44: tensorflow.metadata.v0.StructDomain.sparse_feature:type_name -> tensorflow.metadata.v0.SparseFeature + 2, // 45: tensorflow.metadata.v0.TimeDomain.integer_format:type_name -> tensorflow.metadata.v0.TimeDomain.IntegerTimeFormat + 3, // 46: tensorflow.metadata.v0.TimeOfDayDomain.integer_format:type_name -> tensorflow.metadata.v0.TimeOfDayDomain.IntegerTimeOfDayFormat + 27, // 47: tensorflow.metadata.v0.FeatureComparator.infinity_norm:type_name -> tensorflow.metadata.v0.InfinityNorm + 36, // 48: tensorflow.metadata.v0.TensorRepresentation.dense_tensor:type_name -> tensorflow.metadata.v0.TensorRepresentation.DenseTensor + 37, // 49: tensorflow.metadata.v0.TensorRepresentation.varlen_sparse_tensor:type_name -> tensorflow.metadata.v0.TensorRepresentation.VarLenSparseTensor + 38, // 50: tensorflow.metadata.v0.TensorRepresentation.sparse_tensor:type_name -> tensorflow.metadata.v0.TensorRepresentation.SparseTensor + 39, // 51: tensorflow.metadata.v0.TensorRepresentationGroup.tensor_representation:type_name -> tensorflow.metadata.v0.TensorRepresentationGroup.TensorRepresentationEntry + 30, // 52: tensorflow.metadata.v0.Schema.TensorRepresentationGroupEntry.value:type_name -> tensorflow.metadata.v0.TensorRepresentationGroup + 9, // 53: tensorflow.metadata.v0.TensorRepresentation.DenseTensor.shape:type_name -> tensorflow.metadata.v0.FixedShape + 35, // 54: tensorflow.metadata.v0.TensorRepresentation.DenseTensor.default_value:type_name -> tensorflow.metadata.v0.TensorRepresentation.DefaultValue + 9, // 55: tensorflow.metadata.v0.TensorRepresentation.SparseTensor.dense_shape:type_name -> tensorflow.metadata.v0.FixedShape + 29, // 56: tensorflow.metadata.v0.TensorRepresentationGroup.TensorRepresentationEntry.value:type_name -> tensorflow.metadata.v0.TensorRepresentation + 57, // [57:57] is the sub-list for method output_type + 57, // [57:57] is the sub-list for method input_type + 57, // [57:57] is the sub-list for extension type_name + 57, // [57:57] is the sub-list for extension extendee + 0, // [0:57] is the sub-list for field type_name +} + +func init() { file_tensorflow_metadata_proto_v0_schema_proto_init() } +func file_tensorflow_metadata_proto_v0_schema_proto_init() { + if File_tensorflow_metadata_proto_v0_schema_proto != nil { + return + } + file_tensorflow_metadata_proto_v0_path_proto_init() + if !protoimpl.UnsafeEnabled { + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Schema); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Feature); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Annotation); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NumericValueComparator); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DatasetConstraints); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FixedShape); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ValueCount); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WeightedFeature); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SparseFeature); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DistributionConstraints); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*IntDomain); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FloatDomain); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StructDomain); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StringDomain); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BoolDomain); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NaturalLanguageDomain); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ImageDomain); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MIDDomain); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*URLDomain); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TimeDomain); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TimeOfDayDomain); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FeaturePresence); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FeaturePresenceWithinGroup); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*InfinityNorm); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FeatureComparator); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TensorRepresentation); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TensorRepresentationGroup); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FixedShape_Dim); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SparseFeature_IndexFeature); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SparseFeature_ValueFeature); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TensorRepresentation_DefaultValue); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TensorRepresentation_DenseTensor); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TensorRepresentation_VarLenSparseTensor); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TensorRepresentation_SparseTensor); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[1].OneofWrappers = []interface{}{ + (*Feature_Presence)(nil), + (*Feature_GroupPresence)(nil), + (*Feature_Shape)(nil), + (*Feature_ValueCount)(nil), + (*Feature_Domain)(nil), + (*Feature_IntDomain)(nil), + (*Feature_FloatDomain)(nil), + (*Feature_StringDomain)(nil), + (*Feature_BoolDomain)(nil), + (*Feature_StructDomain)(nil), + (*Feature_NaturalLanguageDomain)(nil), + (*Feature_ImageDomain)(nil), + (*Feature_MidDomain)(nil), + (*Feature_UrlDomain)(nil), + (*Feature_TimeDomain)(nil), + (*Feature_TimeOfDayDomain)(nil), + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[19].OneofWrappers = []interface{}{ + (*TimeDomain_StringFormat)(nil), + (*TimeDomain_IntegerFormat)(nil), + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[20].OneofWrappers = []interface{}{ + (*TimeOfDayDomain_StringFormat)(nil), + (*TimeOfDayDomain_IntegerFormat)(nil), + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[25].OneofWrappers = []interface{}{ + (*TensorRepresentation_DenseTensor_)(nil), + (*TensorRepresentation_VarlenSparseTensor)(nil), + (*TensorRepresentation_SparseTensor_)(nil), + } + file_tensorflow_metadata_proto_v0_schema_proto_msgTypes[31].OneofWrappers = []interface{}{ + (*TensorRepresentation_DefaultValue_FloatValue)(nil), + (*TensorRepresentation_DefaultValue_IntValue)(nil), + (*TensorRepresentation_DefaultValue_BytesValue)(nil), + (*TensorRepresentation_DefaultValue_UintValue)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_tensorflow_metadata_proto_v0_schema_proto_rawDesc, + NumEnums: 4, + NumMessages: 36, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_tensorflow_metadata_proto_v0_schema_proto_goTypes, + DependencyIndexes: file_tensorflow_metadata_proto_v0_schema_proto_depIdxs, + EnumInfos: file_tensorflow_metadata_proto_v0_schema_proto_enumTypes, + MessageInfos: file_tensorflow_metadata_proto_v0_schema_proto_msgTypes, + }.Build() + File_tensorflow_metadata_proto_v0_schema_proto = out.File + file_tensorflow_metadata_proto_v0_schema_proto_rawDesc = nil + file_tensorflow_metadata_proto_v0_schema_proto_goTypes = nil + file_tensorflow_metadata_proto_v0_schema_proto_depIdxs = nil +} diff --git a/sdk/go/request.go b/sdk/go/request.go index 9e97dffb72f..683f17c16ed 100644 --- a/sdk/go/request.go +++ b/sdk/go/request.go @@ -2,36 +2,34 @@ package feast import ( "fmt" - "github.com/gojek/feast/sdk/go/protos/feast/serving" - "strconv" + "github.com/feast-dev/feast/sdk/go/protos/feast/serving" "strings" ) var ( - ErrInvalidFeatureName = "invalid feature ids %s provided, feature names must be in the format /:" + // ErrInvalidFeatureRef indicates that the user has provided a feature reference + // with the wrong structure or contents + ErrInvalidFeatureRef = "Invalid Feature Reference %s provided, " + + "feature reference must be in the format [featureset:]name" ) // OnlineFeaturesRequest wrapper on feast.serving.GetOnlineFeaturesRequest. type OnlineFeaturesRequest struct { // Features is the list of features to obtain from Feast. Each feature can be given as - // - // : - // / - // /: - // The only required components are the feature name and project. + // the format feature_set:feature, where "feature_set" & "feature" are feature set name + // and feature name respectively. The only required components is feature name. Features []string // Entities is the list of entity rows to retrieve features on. Each row is a map of entity name to entity value. Entities []Row - // Project is the default project to use when looking up features. This is only used when a project is not found - // within the feature id. + // Project specifies the project would contain the feature sets where the requested features belong to. Project string } // Builds the feast-specified request payload from the wrapper. func (r OnlineFeaturesRequest) buildRequest() (*serving.GetOnlineFeaturesRequest, error) { - features, err := buildFeatures(r.Features, r.Project) + featureRefs, err := buildFeatureRefs(r.Features, r.Project) if err != nil { return nil, err } @@ -44,58 +42,74 @@ func (r OnlineFeaturesRequest) buildRequest() (*serving.GetOnlineFeaturesRequest } } return &serving.GetOnlineFeaturesRequest{ - Features: features, + Features: featureRefs, EntityRows: entityRows, }, nil } -// buildFeatures create a slice of FeatureReferences from a slice of "/:" -// It returns an error when the format is invalid -func buildFeatures(featureReferences []string, defaultProject string) ([]*serving.FeatureReference, error) { - var features []*serving.FeatureReference - - for _, featureRef := range featureReferences { - var project string - var name string - var version int - var featureSplit []string - - projectSplit := strings.Split(featureRef, "/") - - if len(projectSplit) == 2 { - project = projectSplit[0] - featureSplit = strings.Split(projectSplit[1], ":") - } else if len(projectSplit) == 1 { - project = defaultProject - featureSplit = strings.Split(projectSplit[0], ":") - } else { - return nil, fmt.Errorf(ErrInvalidFeatureName, featureRef) +// Creates a slice of FeatureReferences from string representation in +// the format featureset:feature. +// featureRefStrs - string feature references to parse. +// project - Optionally sets the project in parsed FeatureReferences. Otherwise pass "" +// Returns parsed FeatureReferences. +// Returns an error when the format of the string feature reference is invalid +func buildFeatureRefs(featureRefStrs []string, project string) ([]*serving.FeatureReference, error) { + var featureRefs []*serving.FeatureReference + + for _, featureRefStr := range featureRefStrs { + featureRef, err := parseFeatureRef(featureRefStr, false) + if err != nil { + return nil, err } - - if len(featureSplit) == 2 { - name = featureSplit[0] - v, err := strconv.Atoi(featureSplit[1]) - if err != nil { - return nil, fmt.Errorf(ErrInvalidFeatureName, featureRef) - } - version = v - } else if len(featureSplit) == 1 { - name = featureSplit[0] - } else { - return nil, fmt.Errorf(ErrInvalidFeatureName, featureRef) + // apply project if specified + if len(project) != 0 { + featureRef.Project = project } + featureRefs = append(featureRefs, featureRef) + } + return featureRefs, nil +} +// Parses a string FeatureReference into FeatureReference proto +// featureRefStr - the string feature reference to parse. +// ignoreProject - if true would ignore if project is specified in the given featureRefStr +// Otherwise, would return an error if project is detected in featureRefStr. +// Returns parsed FeatureReference. +// Returns an error when the format of the string feature reference is invalid +func parseFeatureRef(featureRefStr string, ignoreProject bool) (*serving.FeatureReference, error) { + if len(featureRefStr) == 0 { + return nil, fmt.Errorf(ErrInvalidFeatureRef, featureRefStr) + } - if project == "" || name == "" || version < 0 { - return nil, fmt.Errorf(ErrInvalidFeatureName, featureRef) + var featureRef serving.FeatureReference + if strings.Contains(featureRefStr, "/") { + if ignoreProject { + projectSplit := strings.Split(featureRefStr, "/") + featureRefStr = projectSplit[1] + } else { + return nil, fmt.Errorf(ErrInvalidFeatureRef, featureRefStr) } + } + // parse featureset if specified + if strings.Contains(featureRefStr, ":") { + refSplit := strings.Split(featureRefStr, ":") + featureRef.FeatureSet, featureRefStr = refSplit[0], refSplit[1] + } + featureRef.Name = featureRefStr + + return &featureRef, nil +} - features = append(features, &serving.FeatureReference{ - Name: name, - Version: int32(version), - Project: project, - }) +// Converts a FeatureReference proto into a string +// featureRef - The FeatureReference to render as string +// Returns string representation of the given FeatureReference +func toFeatureRefStr(featureRef *serving.FeatureReference) string { + refStr := "" + // In protov3, unset string and default to "" + if len(featureRef.FeatureSet) > 0 { + refStr += featureRef.FeatureSet + ":" } + refStr += featureRef.Name - return features, nil + return refStr } diff --git a/sdk/go/request_test.go b/sdk/go/request_test.go index 2e403f0bd3e..70e1f0d8f5e 100644 --- a/sdk/go/request_test.go +++ b/sdk/go/request_test.go @@ -2,10 +2,10 @@ package feast import ( "fmt" - "github.com/gojek/feast/sdk/go/protos/feast/serving" - "github.com/gojek/feast/sdk/go/protos/feast/types" + "github.com/feast-dev/feast/sdk/go/protos/feast/serving" + "github.com/feast-dev/feast/sdk/go/protos/feast/types" json "github.com/golang/protobuf/jsonpb" - "github.com/google/go-cmp/cmp" + "github.com/golang/protobuf/proto" "testing" ) @@ -20,40 +20,27 @@ func TestGetOnlineFeaturesRequest(t *testing.T) { { name: "valid", req: OnlineFeaturesRequest{ - Features: []string{"my_project_1/feature1:1", "my_project_2/feature1:1", "my_project_4/feature3", "feature2:2", "feature2"}, + Features: []string{ + "driver:driver_id", + "driver_id", + }, Entities: []Row{ {"entity1": Int64Val(1), "entity2": StrVal("bob")}, {"entity1": Int64Val(1), "entity2": StrVal("annie")}, {"entity1": Int64Val(1), "entity2": StrVal("jane")}, }, - Project: "my_project_3", + Project: "driver_project", }, want: &serving.GetOnlineFeaturesRequest{ Features: []*serving.FeatureReference{ { - Project: "my_project_1", - Name: "feature1", - Version: 1, - }, - { - Project: "my_project_2", - Name: "feature1", - Version: 1, - }, - { - Project: "my_project_4", - Name: "feature3", - Version: 0, + Project: "driver_project", + FeatureSet: "driver", + Name: "driver_id", }, { - Project: "my_project_3", - Name: "feature2", - Version: 2, - }, - { - Project: "my_project_3", - Name: "feature2", - Version: 0, + Project: "driver_project", + Name: "driver_id", }, }, EntityRows: []*serving.GetOnlineFeaturesRequest_EntityRow{ @@ -81,54 +68,15 @@ func TestGetOnlineFeaturesRequest(t *testing.T) { wantErr: false, err: nil, }, - { - name: "valid_project_in_name", - req: OnlineFeaturesRequest{ - Features: []string{"project/feature1"}, - Entities: []Row{}, - }, - want: &serving.GetOnlineFeaturesRequest{ - Features: []*serving.FeatureReference{ - { - Project: "project", - Name: "feature1", - Version: 0, - }, - }, - EntityRows: []*serving.GetOnlineFeaturesRequest_EntityRow{ - }, - OmitEntitiesInResponse: false, - }, - wantErr: false, - err: nil, - }, - { - name: "no_project", - req: OnlineFeaturesRequest{ - Features: []string{"feature1"}, - Entities: []Row{}, - }, - wantErr: true, - err: fmt.Errorf(ErrInvalidFeatureName, "feature1"), - }, { name: "invalid_feature_name/wrong_format", req: OnlineFeaturesRequest{ - Features: []string{"fs1:3:feature1"}, - Entities: []Row{}, - Project: "my_project", - }, - wantErr: true, - err: fmt.Errorf(ErrInvalidFeatureName, "fs1:3:feature1"), - }, - { - name: "invalid_feature_name/invalid_version", - req: OnlineFeaturesRequest{ - Features: []string{"project/a:feature1"}, + Features: []string{"/fs1:feature1"}, Entities: []Row{}, + Project: "my_project", }, wantErr: true, - err: fmt.Errorf(ErrInvalidFeatureName, "project/a:feature1"), + err: fmt.Errorf(ErrInvalidFeatureRef, "/fs1:feature1"), }, } for _, tc := range tt { @@ -145,11 +93,11 @@ func TestGetOnlineFeaturesRequest(t *testing.T) { return } - if !cmp.Equal(got, tc.want) { + if !proto.Equal(got, tc.want) { m := json.Marshaler{} - gotJson, _ := m.MarshalToString(got) - wantJson, _ := m.MarshalToString(tc.want) - t.Errorf("got: \n%v\nwant:\n%v", gotJson, wantJson) + gotJSON, _ := m.MarshalToString(got) + wantJSON, _ := m.MarshalToString(tc.want) + t.Errorf("got: \n%v\nwant:\n%v", gotJSON, wantJSON) } }) } diff --git a/sdk/go/response.go b/sdk/go/response.go index 086321cacdf..1bc80af9fbb 100644 --- a/sdk/go/response.go +++ b/sdk/go/response.go @@ -2,13 +2,18 @@ package feast import ( "fmt" - "github.com/gojek/feast/sdk/go/protos/feast/serving" - "github.com/gojek/feast/sdk/go/protos/feast/types" + "github.com/feast-dev/feast/sdk/go/protos/feast/serving" + "github.com/feast-dev/feast/sdk/go/protos/feast/types" ) var ( + // ErrLengthMismatch indicates that the number of values returned is not the same as the number of values requested ErrLengthMismatch = "Length mismatch; number of na values (%d) not equal to number of features requested (%d)." + + // ErrFeatureNotFound indicates that the a requested feature was not found in the response ErrFeatureNotFound = "Feature %s not found in response." + + // ErrTypeMismatch indicates that the there was a type mismatch in the returned values ErrTypeMismatch = "Requested output of type %s does not match type of feature value returned." ) @@ -20,7 +25,7 @@ type OnlineFeaturesResponse struct { // Rows retrieves the result of the request as a list of Rows. func (r OnlineFeaturesResponse) Rows() []Row { rows := make([]Row, len(r.RawResponse.FieldValues)) - for i, val := range r.RawResponse.FieldValues { + for i, val := range r.RawResponse.FieldValues { rows[i] = val.Fields } return rows @@ -33,7 +38,7 @@ func (r OnlineFeaturesResponse) Int64Arrays(order []string, fillNa []int64) ([][ if len(fillNa) != len(order) { return nil, fmt.Errorf(ErrLengthMismatch, len(fillNa), len(order)) } - for i, val := range r.RawResponse.FieldValues { + for i, val := range r.RawResponse.FieldValues { rows[i] = make([]int64, len(order)) for j, fname := range order { fValue, exists := val.Fields[fname] @@ -60,7 +65,7 @@ func (r OnlineFeaturesResponse) Float64Arrays(order []string, fillNa []float64) if len(fillNa) != len(order) { return nil, fmt.Errorf(ErrLengthMismatch, len(fillNa), len(order)) } - for i, val := range r.RawResponse.FieldValues { + for i, val := range r.RawResponse.FieldValues { rows[i] = make([]float64, len(order)) for j, fname := range order { fValue, exists := val.Fields[fname] diff --git a/sdk/go/response_test.go b/sdk/go/response_test.go index 882c1695d59..6a7c4211187 100644 --- a/sdk/go/response_test.go +++ b/sdk/go/response_test.go @@ -2,29 +2,29 @@ package feast import ( "fmt" - "github.com/gojek/feast/sdk/go/protos/feast/serving" - "github.com/gojek/feast/sdk/go/protos/feast/types" + "github.com/feast-dev/feast/sdk/go/protos/feast/serving" + "github.com/feast-dev/feast/sdk/go/protos/feast/types" "github.com/google/go-cmp/cmp" "testing" ) var response = OnlineFeaturesResponse{ -RawResponse: &serving.GetOnlineFeaturesResponse{ - FieldValues: []*serving.GetOnlineFeaturesResponse_FieldValues{ - { - Fields: map[string]*types.Value{ - "project1/feature1": Int64Val(1), - "project1/feature2": &types.Value{}, + RawResponse: &serving.GetOnlineFeaturesResponse{ + FieldValues: []*serving.GetOnlineFeaturesResponse_FieldValues{ + { + Fields: map[string]*types.Value{ + "project1/feature1": Int64Val(1), + "project1/feature2": {}, + }, }, - }, - { - Fields: map[string]*types.Value{ - "project1/feature1": Int64Val(2), - "project1/feature2": Int64Val(2), + { + Fields: map[string]*types.Value{ + "project1/feature1": Int64Val(2), + "project1/feature2": Int64Val(2), + }, }, }, }, -}, } func TestOnlineFeaturesResponseToRow(t *testing.T) { @@ -33,14 +33,19 @@ func TestOnlineFeaturesResponseToRow(t *testing.T) { {"project1/feature1": Int64Val(1), "project1/feature2": &types.Value{}}, {"project1/feature1": Int64Val(2), "project1/feature2": Int64Val(2)}, } - if !cmp.Equal(actual, expected) { + if len(expected) != len(actual) { t.Errorf("expected: %v, got: %v", expected, actual) } + for i := range expected { + if !expected[i].equalTo(actual[i]) { + t.Errorf("expected: %v, got: %v", expected, actual) + } + } } func TestOnlineFeaturesResponseToInt64Array(t *testing.T) { type args struct { - order []string + order []string fillNa []int64 } tt := []struct { @@ -53,31 +58,31 @@ func TestOnlineFeaturesResponseToInt64Array(t *testing.T) { { name: "valid", args: args{ - order: []string{"project1/feature2", "project1/feature1" }, + order: []string{"project1/feature2", "project1/feature1"}, fillNa: []int64{-1, -1}, }, - want: [][]int64{{-1, 1}, {2, 2}}, + want: [][]int64{{-1, 1}, {2, 2}}, wantErr: false, }, { name: "length mismatch", args: args{ - order: []string{"fs:1:feature2", "fs:1:feature1"}, + order: []string{"fs:feature2", "fs:feature1"}, fillNa: []int64{-1}, }, - want: nil, + want: nil, wantErr: true, - err: fmt.Errorf(ErrLengthMismatch, 1, 2), + err: fmt.Errorf(ErrLengthMismatch, 1, 2), }, { name: "length mismatch", args: args{ - order: []string{"project1/feature2", "project1/feature3" }, + order: []string{"project1/feature2", "project1/feature3"}, fillNa: []int64{-1, -1}, }, - want: nil, + want: nil, wantErr: true, - err: fmt.Errorf(ErrFeatureNotFound, "project1/feature3"), + err: fmt.Errorf(ErrFeatureNotFound, "project1/feature3"), }, } for _, tc := range tt { @@ -96,4 +101,4 @@ func TestOnlineFeaturesResponseToInt64Array(t *testing.T) { } }) } -} \ No newline at end of file +} diff --git a/sdk/go/types.go b/sdk/go/types.go index 74606982a4a..7fe400e45bb 100644 --- a/sdk/go/types.go +++ b/sdk/go/types.go @@ -1,9 +1,26 @@ package feast -import "github.com/gojek/feast/sdk/go/protos/feast/types" +import ( + "github.com/feast-dev/feast/sdk/go/protos/feast/types" + "github.com/golang/protobuf/proto" +) +// Row map of entity values type Row map[string]*types.Value +func (r Row) equalTo(other Row) bool { + for k, v := range r { + if otherV, ok := other[k]; !ok { + return false + } else { + if !proto.Equal(v, otherV) { + return false + } + } + } + return true +} + // StrVal is a int64 type feast value func StrVal(val string) *types.Value { return &types.Value{Val: &types.Value_StringVal{StringVal: val}} @@ -37,4 +54,4 @@ func BoolVal(val bool) *types.Value { // BytesVal is a bytes type feast value func BytesVal(val []byte) *types.Value { return &types.Value{Val: &types.Value_BytesVal{BytesVal: val}} -} \ No newline at end of file +} diff --git a/sdk/java/pom.xml b/sdk/java/pom.xml index 2970dae3ee2..75d82edcc01 100644 --- a/sdk/java/pom.xml +++ b/sdk/java/pom.xml @@ -21,6 +21,12 @@ + + dev.feast + datatypes-java + ${project.version} + + io.grpc @@ -79,10 +85,6 @@ - - org.xolstice.maven.plugins - protobuf-maven-plugin - org.apache.maven.plugins @@ -92,12 +94,8 @@ - maven-surefire-plugin - 2.22.2 - - - maven-failsafe-plugin - 2.22.2 + org.jacoco + jacoco-maven-plugin diff --git a/sdk/java/src/main/java/com/gojek/feast/FeastClient.java b/sdk/java/src/main/java/com/gojek/feast/FeastClient.java index 8014231836e..c6e7edd0764 100644 --- a/sdk/java/src/main/java/com/gojek/feast/FeastClient.java +++ b/sdk/java/src/main/java/com/gojek/feast/FeastClient.java @@ -16,15 +16,17 @@ */ package com.gojek.feast; -import feast.serving.ServingAPIProto.FeatureReference; -import feast.serving.ServingAPIProto.GetFeastServingInfoRequest; -import feast.serving.ServingAPIProto.GetFeastServingInfoResponse; -import feast.serving.ServingAPIProto.GetOnlineFeaturesRequest; -import feast.serving.ServingAPIProto.GetOnlineFeaturesRequest.EntityRow; -import feast.serving.ServingAPIProto.GetOnlineFeaturesResponse; -import feast.serving.ServingServiceGrpc; +import feast.proto.serving.ServingAPIProto.FeatureReference; +import feast.proto.serving.ServingAPIProto.GetFeastServingInfoRequest; +import feast.proto.serving.ServingAPIProto.GetFeastServingInfoResponse; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequest; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequest.EntityRow; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse; +import feast.proto.serving.ServingServiceGrpc; +import feast.proto.types.ValueProto.Value; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; +import java.util.HashSet; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -56,61 +58,78 @@ public GetFeastServingInfoResponse getFeastServingInfo() { return stub.getFeastServingInfo(GetFeastServingInfoRequest.newBuilder().build()); } + /** + * Get online features from Feast from FeatureSets + * + *

See {@link #getOnlineFeatures(List, List, String, boolean)} + * + * @param featureRefs list of string feature references to retrieve in the following format + * featureSet:feature, where 'featureSet' and 'feature' refer to the FeatureSet and Feature + * names respectively. Only the Feature name is required. + * @param rows list of {@link Row} to select the entities to retrieve the features for. + * @return list of {@link Row} containing retrieved data fields. + */ + public List getOnlineFeatures(List featureRefs, List rows) { + return getOnlineFeatures(featureRefs, rows, ""); + } + /** * Get online features from Feast. * - *

See {@link #getOnlineFeatures(List, List, String)} + *

See {@link #getOnlineFeatures(List, List, String, boolean)} * - * @param features list of string feature references to retrieve, feature reference follows this - * format [project]/[name]:[version] + * @param featureRefs list of string feature references to retrieve in the following format + * featureSet:feature, where 'featureSet' and 'feature' refer to the FeatureSet and Feature + * names respectively. Only the Feature name is required. * @param rows list of {@link Row} to select the entities to retrieve the features for - * @param defaultProject {@link String} Default project to find features in if not provided in - * feature reference. - * @return list of {@link Row} containing features + * @param project {@link String} Specifies the project which contains the FeatureSets which the + * Feature requested belong to. + * @return list of {@link Row} containing retrieved data fields. */ - public List getOnlineFeatures(List features, List rows, String defaultProject) { - return getOnlineFeatures(features, rows, defaultProject, false); + public List getOnlineFeatures(List featureRefs, List rows, String project) { + return getOnlineFeatures(featureRefs, rows, project, false); } /** * Get online features from Feast. * - *

Example of retrieving online features for the driver project, with features driver_id and - * driver_name, both version 1 + *

Example of retrieving online features for the driver featureset, with features driver_id and + * driver_name * *

{@code
    * FeastClient client = FeastClient.create("localhost", 6566);
-   * List requestedFeatureIds = Arrays.asList("driver/driver_id:1", "driver/driver_name:1");
+   * List requestedFeatureIds = Arrays.asList("driver:driver_id", "driver:driver_name");
    * List requestedRows =
    *         Arrays.asList(Row.create().set("driver_id", 123), Row.create().set("driver_id", 456));
    * List retrievedFeatures = client.getOnlineFeatures(requestedFeatureIds, requestedRows);
    * retrievedFeatures.forEach(System.out::println);
    * }
* - * @param featureRefStrings list of feature refs to retrieve, feature refs follow this format - * [project]/[name]:[version] + * @param featureRefs list of string feature references to retrieve in the following format + * featureSet:feature, where 'featureSet' and 'feature' refer to the FeatureSet and Feature + * names respectively. Only the Feature name is required. * @param rows list of {@link Row} to select the entities to retrieve the features for - * @param defaultProject {@link String} Default project to find features in if not provided in - * feature reference. + * @param project {@link String} Specifies the project which contains the FeatureSets which the + * Feature requested belong to. * @param omitEntitiesInResponse if true, the returned {@link Row} will not contain field and * value for the entity - * @return list of {@link Row} containing features + * @return list of {@link Row} containing retrieved data fields. */ public List getOnlineFeatures( - List featureRefStrings, - List rows, - String defaultProject, - boolean omitEntitiesInResponse) { - List features = - RequestUtil.createFeatureRefs(featureRefStrings, defaultProject); + List featureRefs, List rows, String project, boolean omitEntitiesInResponse) { + List features = RequestUtil.createFeatureRefs(featureRefs, project); + // build entity rows and collect entity references + HashSet entityRefs = new HashSet<>(); List entityRows = rows.stream() .map( - row -> - EntityRow.newBuilder() - .setEntityTimestamp(row.getEntityTimestamp()) - .putAllFields(row.getFields()) - .build()) + row -> { + entityRefs.addAll(row.getFields().keySet()); + return EntityRow.newBuilder() + .setEntityTimestamp(row.getEntityTimestamp()) + .putAllFields(row.getFields()) + .build(); + }) .collect(Collectors.toList()); GetOnlineFeaturesResponse response = @@ -125,7 +144,18 @@ public List getOnlineFeatures( .map( field -> { Row row = Row.create(); - field.getFieldsMap().forEach(row::set); + field + .getFieldsMap() + .forEach( + (String name, Value value) -> { + // Strip project from string Feature References from returned from serving + if (!entityRefs.contains(name)) { + FeatureReference featureRef = + RequestUtil.parseFeatureRef(name, true).build(); + name = RequestUtil.renderFeatureRef(featureRef); + } + row.set(name, value); + }); return row; }) .collect(Collectors.toList()); diff --git a/sdk/java/src/main/java/com/gojek/feast/RequestUtil.java b/sdk/java/src/main/java/com/gojek/feast/RequestUtil.java index 075c570c4e9..3a1a0919d3e 100644 --- a/sdk/java/src/main/java/com/gojek/feast/RequestUtil.java +++ b/sdk/java/src/main/java/com/gojek/feast/RequestUtil.java @@ -16,76 +16,90 @@ */ package com.gojek.feast; -import feast.serving.ServingAPIProto.FeatureReference; -import java.util.ArrayList; +import feast.proto.serving.ServingAPIProto.FeatureReference; import java.util.List; +import java.util.stream.Collectors; @SuppressWarnings("WeakerAccess") public class RequestUtil { + /** + * Create feature references protos from given string feature reference. + * + * @param featureRefStrings to create Feature Reference protos from + * @param project specifies to the project set in parsed Feature Reference protos otherwise "" + * @return List of parsed {@link FeatureReference} protos + */ public static List createFeatureRefs( - List featureRefStrings, String defaultProject) { + List featureRefStrings, String project) { if (featureRefStrings == null) { throw new IllegalArgumentException("featureRefs cannot be null"); } - List featureRefs = new ArrayList<>(); + List featureRefs = + featureRefStrings.stream() + .map(refStr -> parseFeatureRef(refStr, false)) + .collect(Collectors.toList()); + // apply project override if specified + if (!project.isEmpty()) { + featureRefs = + featureRefs.stream().map(ref -> ref.setProject(project)).collect(Collectors.toList()); + } - for (String featureRefString : featureRefStrings) { - String project; - String name; - int version = 0; - String[] featureSplit; - String[] projectSplit = featureRefString.split("/"); + return featureRefs.stream().map(ref -> ref.build()).collect(Collectors.toList()); + } - if (projectSplit.length == 2) { - project = projectSplit[0]; - featureSplit = projectSplit[1].split(":"); - } else if (projectSplit.length == 1) { - project = defaultProject; - featureSplit = projectSplit[0].split(":"); - } else { - throw new IllegalArgumentException( - String.format( - "Feature id '%s' has invalid format. Expected format: ::.", - featureRefString)); - } + /** + * Parse a feature reference proto builder from the given featureRefString + * + * @param featureRefString string feature reference to parse from. + * @param ignoreProject If true, would ignore if project is specified in given ref string. + * Otherwise, throwws a {@link IllegalArgumentException} if project is specified. + * @return a parsed {@link FeatureReference.Builder} + */ + public static FeatureReference.Builder parseFeatureRef( + String featureRefString, boolean ignoreProject) { + featureRefString = featureRefString.trim(); + if (featureRefString.isEmpty()) { + throw new IllegalArgumentException("Cannot parse a empty feature reference"); + } + FeatureReference.Builder featureRef = FeatureReference.newBuilder(); - if (featureSplit.length == 2) { - name = featureSplit[0]; - try { - version = Integer.parseInt(featureSplit[1]); - } catch (NumberFormatException e) { - throw new IllegalArgumentException( - String.format( - "Feature id '%s' contains invalid version. Expected format: /:.", - featureRefString)); - } - } else if (projectSplit.length == 1) { - name = featureSplit[0]; + // parse project if specified + if (featureRefString.contains("/")) { + if (ignoreProject) { + String[] projectSplit = featureRefString.split("/"); + featureRefString = projectSplit[1]; } else { throw new IllegalArgumentException( - String.format( - "Feature id '%s' has invalid format. Expected format: /:.", - featureRefString)); + String.format("Unsupported feature reference: %s", featureRefString)); } + } - if (project.isEmpty() || name.isEmpty() || version < 0) { - throw new IllegalArgumentException( - String.format( - "Feature id '%s' has invalid format. Expected format: /:.", - featureRefString)); - } + // parse featureset if specified + if (featureRefString.contains(":")) { + String[] featureSetSplit = featureRefString.split(":"); + featureRef.setFeatureSet(featureSetSplit[0]); + featureRefString = featureSetSplit[1]; + } + featureRef.setName(featureRefString); + return featureRef; + } - featureRefs.add( - FeatureReference.newBuilder() - .setName(name) - .setProject(project) - .setVersion(version) - .build()); + /** + * Render a feature reference as string. + * + * @param featureReference to render as string + * @return string represenation of feature reference. + */ + public static String renderFeatureRef(FeatureReference featureReference) { + String refStr = ""; + // In protov3, unset string and int fields default to "" and 0 respectively + if (!featureReference.getFeatureSet().isEmpty()) { + refStr += featureReference.getFeatureSet() + ":"; } + refStr = refStr + featureReference.getName(); - ; - return featureRefs; + return refStr; } } diff --git a/sdk/java/src/main/java/com/gojek/feast/Row.java b/sdk/java/src/main/java/com/gojek/feast/Row.java index 9366fe1bb03..2ac2581cac9 100644 --- a/sdk/java/src/main/java/com/gojek/feast/Row.java +++ b/sdk/java/src/main/java/com/gojek/feast/Row.java @@ -19,8 +19,8 @@ import com.google.protobuf.ByteString; import com.google.protobuf.Timestamp; import com.google.protobuf.util.Timestamps; -import feast.types.ValueProto.Value; -import feast.types.ValueProto.Value.ValCase; +import feast.proto.types.ValueProto.Value; +import feast.proto.types.ValueProto.Value.ValCase; import java.time.Instant; import java.util.ArrayList; import java.util.HashMap; @@ -31,7 +31,7 @@ @SuppressWarnings("UnusedReturnValue") public class Row { private Timestamp entity_timestamp; - private Map fields; + private HashMap fields; public static Row create() { Row row = new Row(); diff --git a/sdk/java/src/test/java/com/gojek/feast/RequestUtilTest.java b/sdk/java/src/test/java/com/gojek/feast/RequestUtilTest.java index 1c58e9435c6..f10d82b8726 100644 --- a/sdk/java/src/test/java/com/gojek/feast/RequestUtilTest.java +++ b/sdk/java/src/test/java/com/gojek/feast/RequestUtilTest.java @@ -20,11 +20,11 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.google.protobuf.TextFormat; -import feast.serving.ServingAPIProto.FeatureReference; +import feast.proto.serving.ServingAPIProto.FeatureReference; import java.util.Arrays; -import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -33,57 +33,27 @@ class RequestUtilTest { - private static Stream provideValidFeatureIds() { + private static Stream provideValidFeatureRefs() { return Stream.of( Arguments.of( - Collections.singletonList("driver_project/driver_id:1"), - Collections.singletonList( - FeatureReference.newBuilder() - .setProject("driver_project") - .setName("driver_id") - .setVersion(1) - .build())), - Arguments.of( - Arrays.asList("driver_project/driver_id:1", "driver_project/driver_name:1"), + Arrays.asList("driver:driver_id", "driver_id"), Arrays.asList( FeatureReference.newBuilder() .setProject("driver_project") + .setFeatureSet("driver") .setName("driver_id") - .setVersion(1) .build(), FeatureReference.newBuilder() .setProject("driver_project") - .setName("driver_name") - .setVersion(1) - .build())), - Arguments.of( - Arrays.asList( - "driver_project/driver_id:1", - "driver_project/driver_name:1", - "booking_project/driver_name:1"), - Arrays.asList( - FeatureReference.newBuilder() - .setProject("driver_project") - .setVersion(1) .setName("driver_id") - .build(), - FeatureReference.newBuilder() - .setProject("driver_project") - .setVersion(1) - .setName("driver_name") - .build(), - FeatureReference.newBuilder() - .setProject("booking_project") - .setVersion(1) - .setName("driver_name") .build()))); } @ParameterizedTest - @MethodSource("provideValidFeatureIds") - void createFeatureSets_ShouldReturnFeatureSetsForValidFeatureIds( + @MethodSource("provideValidFeatureRefs") + void createFeatureSets_ShouldReturnFeatureSetsForValidFeatureRefs( List input, List expected) { - List actual = RequestUtil.createFeatureRefs(input, "my-project"); + List actual = RequestUtil.createFeatureRefs(input, "driver_project"); // Order of the actual and expected featureSets do no not matter actual.sort(Comparator.comparing(FeatureReference::getName)); expected.sort(Comparator.comparing(FeatureReference::getName)); @@ -95,23 +65,35 @@ void createFeatureSets_ShouldReturnFeatureSetsForValidFeatureIds( } } + @ParameterizedTest + @MethodSource("provideValidFeatureRefs") + void renderFeatureRef_ShouldReturnFeatureRefString( + List expected, List input) { + input = + input.stream() + .map(ref -> ref.toBuilder().clearProject().build()) + .collect(Collectors.toList()); + List actual = + input.stream().map(ref -> RequestUtil.renderFeatureRef(ref)).collect(Collectors.toList()); + assertEquals(expected.size(), actual.size()); + for (int i = 0; i < expected.size(); i++) { + assertEquals(expected.get(i), actual.get(i)); + } + } + private static Stream provideInvalidFeatureRefs() { - return Stream.of( - Arguments.of(Collections.singletonList("missing:bad_version")), - Arguments.of(Collections.singletonList(""))); + return Stream.of(Arguments.of(List.of("project/feature", ""))); } @ParameterizedTest @MethodSource("provideInvalidFeatureRefs") void createFeatureSets_ShouldThrowExceptionForInvalidFeatureRefs(List input) { - assertThrows( - IllegalArgumentException.class, () -> RequestUtil.createFeatureRefs(input, "my-project")); + assertThrows(IllegalArgumentException.class, () -> RequestUtil.createFeatureRefs(input, "")); } @ParameterizedTest @NullSource void createFeatureSets_ShouldThrowExceptionForNullFeatureRefs(List input) { - assertThrows( - IllegalArgumentException.class, () -> RequestUtil.createFeatureRefs(input, "my-project")); + assertThrows(IllegalArgumentException.class, () -> RequestUtil.createFeatureRefs(input, "")); } } diff --git a/sdk/python/Makefile b/sdk/python/Makefile deleted file mode 100644 index f940812ef98..00000000000 --- a/sdk/python/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# -# Copyright 2019 The Feast Authors -# -# 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. -# - -TEST_PATH=./ - -test: - pytest --verbose --color=yes $(TEST_PATH) diff --git a/sdk/python/docs/conf.py b/sdk/python/docs/conf.py index 999c191ee54..8a72b7456b5 100644 --- a/sdk/python/docs/conf.py +++ b/sdk/python/docs/conf.py @@ -18,8 +18,8 @@ # import os import sys -import sphinx_rtd_theme +import sphinx_rtd_theme sys.path.insert(0, os.path.abspath("../../feast")) sys.path.insert(0, os.path.abspath("../..")) diff --git a/sdk/python/docs/requirements.txt b/sdk/python/docs/requirements.txt index a7e825b5d49..3a9bbfee453 100644 --- a/sdk/python/docs/requirements.txt +++ b/sdk/python/docs/requirements.txt @@ -18,6 +18,14 @@ fastavro==0.21.* grpcio-testing==1.* pytest-ordering==0.6.* pyarrow +Click==7.* +toml==0.10.* +tqdm==4.* +confluent_kafka +google +pandavro==1.5.* +kafka-python==1.* +tabulate==0.8.* Sphinx==2.* sphinx-autodoc-napoleon-typehints sphinx-autodoc-typehints @@ -28,4 +36,4 @@ sphinxcontrib-htmlhelp sphinxcontrib-jsmath sphinxcontrib-napoleon sphinxcontrib-qthelp -sphinxcontrib-serializinghtml +sphinxcontrib-serializinghtml \ No newline at end of file diff --git a/sdk/python/feast/__init__.py b/sdk/python/feast/__init__.py index adcac0cd248..8342de4c9b0 100644 --- a/sdk/python/feast/__init__.py +++ b/sdk/python/feast/__init__.py @@ -1,4 +1,11 @@ -from pkg_resources import get_distribution, DistributionNotFound +from pkg_resources import DistributionNotFound, get_distribution + +from .client import Client +from .entity import Entity +from .feature import Feature +from .feature_set import FeatureSet +from .source import KafkaSource, Source +from .value_type import ValueType try: __version__ = get_distribution(__name__).version @@ -6,9 +13,12 @@ # package is not installed pass -from .client import Client -from .entity import Entity -from .feature_set import FeatureSet -from .feature import Feature -from .source import Source, KafkaSource -from .value_type import ValueType +__all__ = [ + "Client", + "Entity", + "Feature", + "FeatureSet", + "Source", + "KafkaSource", + "ValueType", +] diff --git a/sdk/python/feast/cli.py b/sdk/python/feast/cli.py index 601f41d4b11..87f12301f86 100644 --- a/sdk/python/feast/cli.py +++ b/sdk/python/feast/cli.py @@ -12,18 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -import sys +import json import logging +import sys + import click -from feast import config as feast_config -from feast.client import Client -from feast.resource import ResourceFactory -from feast.feature_set import FeatureSet -import toml import pkg_resources -from feast.loaders.yaml import yaml_loader import yaml -import json + +from feast.client import Client +from feast.config import Config +from feast.core.IngestionJob_pb2 import IngestionJobStatus +from feast.feature_set import FeatureSet, FeatureSetRef +from feast.loaders.yaml import yaml_loader _logger = logging.getLogger(__name__) @@ -63,14 +64,7 @@ def version(client_only: bool, **kwargs): } if not client_only: - feast_client = Client( - core_url=feast_config.get_config_property_or_fail( - "core_url", force_config=kwargs - ), - serving_url=feast_config.get_config_property_or_fail( - "serving_url", force_config=kwargs - ), - ) + feast_client = Client(**kwargs) feast_versions_dict.update(feast_client.version()) print(json.dumps(feast_versions_dict)) @@ -93,13 +87,8 @@ def config_list(): """ List Feast properties for the currently active configuration """ - try: - feast_config_string = toml.dumps(feast_config._get_or_create_config()) - if not feast_config_string.strip(): - print("Configuration has not been set") - else: - print(feast_config_string.replace('""', "").strip()) + print(Config()) except Exception as e: _logger.error("Error occurred when reading Feast configuration file") _logger.exception(e) @@ -114,7 +103,9 @@ def config_set(prop, value): Set a Feast properties for the currently active configuration """ try: - feast_config.set_property(prop.strip(), value.strip()) + conf = Config() + conf.set(option=prop.strip(), value=value.strip()) + conf.save() except Exception as e: _logger.error("Error in reading config file") _logger.exception(e) @@ -134,48 +125,45 @@ def feature_set_list(): """ List all feature sets """ - feast_client = Client( - core_url=feast_config.get_config_property_or_fail("core_url") - ) # type: Client + feast_client = Client() # type: Client table = [] for fs in feast_client.list_feature_sets(): - table.append([fs.name, fs.version]) + table.append([fs.name, repr(fs)]) from tabulate import tabulate - print(tabulate(table, headers=["NAME", "VERSION"], tablefmt="plain")) + print(tabulate(table, headers=["NAME", "REFERENCE"], tablefmt="plain")) -@feature_set.command("create") -@click.argument("name") -def feature_set_create(name): +@feature_set.command("apply") +# TODO: add project option to overwrite project setting. +@click.option( + "--filename", + "-f", + help="Path to a feature set configuration file that will be applied", + type=click.Path(exists=True), +) +def feature_set_create(filename): """ - Create a feature set + Create or update a feature set """ - feast_client = Client( - core_url=feast_config.get_config_property_or_fail("core_url") - ) # type: Client - feast_client.apply(FeatureSet(name=name)) + feature_sets = [FeatureSet.from_dict(fs_dict) for fs_dict in yaml_loader(filename)] + feast_client = Client() # type: Client + feast_client.apply(feature_sets) @feature_set.command("describe") @click.argument("name", type=click.STRING) -@click.argument("version", type=click.INT) -def feature_set_describe(name: str, version: int): +def feature_set_describe(name: str): """ Describe a feature set """ - feast_client = Client( - core_url=feast_config.get_config_property_or_fail("core_url") - ) # type: Client - - fs = feast_client.get_feature_set(name=name, version=version) + feast_client = Client() # type: Client + fs = feast_client.get_feature_set(name=name) if not fs: - print( - f'Feature set with name "{name}" and version "{version}" could not be found' - ) + print(f'Feature set with name "{name}" could not be found') return print(yaml.dump(yaml.safe_load(str(fs)), default_flow_style=False, sort_keys=False)) @@ -195,9 +183,7 @@ def project_create(name: str): """ Create a project """ - feast_client = Client( - core_url=feast_config.get_config_property_or_fail("core_url") - ) # type: Client + feast_client = Client() # type: Client feast_client.create_project(name) @@ -207,20 +193,16 @@ def project_archive(name: str): """ Archive a project """ - feast_client = Client( - core_url=feast_config.get_config_property_or_fail("core_url") - ) # type: Client + feast_client = Client() # type: Client feast_client.archive_project(name) @project.command(name="list") -def feature_set_list(): +def project_list(): """ List all projects """ - feast_client = Client( - core_url=feast_config.get_config_property_or_fail("core_url") - ) # type: Client + feast_client = Client() # type: Client table = [] for project in feast_client.list_projects(): @@ -231,61 +213,141 @@ def feature_set_list(): print(tabulate(table, headers=["NAME"], tablefmt="plain")) -@cli.command() +@cli.group(name="ingest-jobs") +def ingest_job(): + """ + Manage ingestion jobs + """ + pass + + +@ingest_job.command("list") +@click.option("--job-id", "-i", help="Show only ingestion jobs with the given job id") @click.option( - "--name", "-n", help="Feature set name to ingest data into", required=True + "--feature-set-ref", + "-f", + help="Show only ingestion job targeting the feature set with the given reference", ) @click.option( - "--version", "-v", help="Feature set version to ingest data into", type=int + "--store-name", + "-s", + help="List only ingestion job that ingest into feast store with given name", ) +# TODO: types +def ingest_job_list(job_id, feature_set_ref, store_name): + """ + List ingestion jobs + """ + # parse feature set reference + if feature_set_ref is not None: + feature_set_ref = FeatureSetRef.from_str(feature_set_ref) + + # pull & render ingestion jobs as a table + feast_client = Client() + table = [] + for ingest_job in feast_client.list_ingest_jobs( + job_id=job_id, feature_set_ref=feature_set_ref, store_name=store_name + ): + table.append([ingest_job.id, IngestionJobStatus.Name(ingest_job.status)]) + + from tabulate import tabulate + + print(tabulate(table, headers=["ID", "STATUS"], tablefmt="plain")) + + +@ingest_job.command("describe") +@click.argument("job_id") +def ingest_job_describe(job_id: str): + """ + Describe the ingestion job with the given id. + """ + # find ingestion job for id + feast_client = Client() + jobs = feast_client.list_ingest_jobs(job_id=job_id) + if len(jobs) < 1: + print(f"Ingestion Job with id {job_id} could not be found") + sys.exit(1) + job = jobs[0] + + # pretty render ingestion job as yaml + print( + yaml.dump(yaml.safe_load(str(job)), default_flow_style=False, sort_keys=False) + ) + + +@ingest_job.command("stop") @click.option( - "--filename", - "-f", - help="Path to file to be ingested", - type=click.Path(exists=True), - required=True, + "--wait", "-w", is_flag=True, help="Wait for the ingestion job to fully stop." ) @click.option( - "--file-type", + "--timeout", "-t", - type=click.Choice(["CSV"], case_sensitive=False), - help="Type of file to ingest. Defaults to CSV.", + default=600, + help="Timeout in seconds to wait for the job to stop.", ) -def ingest(name, version, filename, file_type): +@click.argument("job_id") +def ingest_job_stop(wait: bool, timeout: int, job_id: str): """ - Ingest feature data into a feature set + Stop ingestion job for id. """ + # find ingestion job for id + feast_client = Client() + jobs = feast_client.list_ingest_jobs(job_id=job_id) + if len(jobs) < 1: + print(f"Ingestion Job with id {job_id} could not be found") + sys.exit(1) + job = jobs[0] - feast_client = Client( - core_url=feast_config.get_config_property_or_fail("core_url") - ) # type: Client + feast_client.stop_ingest_job(job) - feature_set = feast_client.get_feature_set(name=name, version=version) - feature_set.ingest_file(file_path=filename) + # wait for ingestion job to stop + if wait: + job.wait(IngestionJobStatus.ABORTED, timeout=timeout) + + +@ingest_job.command("restart") +@click.argument("job_id") +def ingest_job_restart(job_id: str): + """ + Restart job for id. + Waits for the job to fully restart. + """ + # find ingestion job for id + feast_client = Client() + jobs = feast_client.list_ingest_jobs(job_id=job_id) + if len(jobs) < 1: + print(f"Ingestion Job with id {job_id} could not be found") + sys.exit(1) + job = jobs[0] + + feast_client.restart_ingest_job(job) @cli.command() +@click.option( + "--name", "-n", help="Feature set name to ingest data into", required=True +) @click.option( "--filename", "-f", - help="Path to the configuration file that will be applied", + help="Path to file to be ingested", type=click.Path(exists=True), + required=True, +) +@click.option( + "--file-type", + "-t", + type=click.Choice(["CSV"], case_sensitive=False), + help="Type of file to ingest. Defaults to CSV.", ) -def apply(filename): +def ingest(name, filename, file_type): """ - Apply a configuration to a resource by filename or stdin + Ingest feature data into a feature set """ - resources = [ - ResourceFactory.get_resource(res_dict["kind"]).from_dict(res_dict) - for res_dict in yaml_loader(filename) - ] - - feast_client = Client( - core_url=feast_config.get_config_property_or_fail("core_url") - ) # type: Client - - feast_client.apply(resources) + feast_client = Client() # type: Client + feature_set = feast_client.get_feature_set(name=name) + feature_set.ingest_file(file_path=filename) if __name__ == "__main__": diff --git a/sdk/python/feast/client.py b/sdk/python/feast/client.py index a68f0fe2bc5..50e3a65bad2 100644 --- a/sdk/python/feast/client.py +++ b/sdk/python/feast/client.py @@ -12,67 +12,73 @@ # See the License for the specific language governing permissions and # limitations under the License. -import json + import logging import os import shutil import tempfile import time +import uuid from collections import OrderedDict from math import ceil -from typing import Dict, List, Tuple, Union, Optional -from typing import List -from urllib.parse import urlparse +from typing import Dict, List, Optional, Tuple, Union -import fastavro import grpc import pandas as pd import pyarrow as pa import pyarrow.parquet as pq + +from feast.config import Config +from feast.constants import ( + CONFIG_CORE_SECURE_KEY, + CONFIG_CORE_URL_KEY, + CONFIG_GRPC_CONNECTION_TIMEOUT_DEFAULT_KEY, + CONFIG_PROJECT_KEY, + CONFIG_SERVING_SECURE_KEY, + CONFIG_SERVING_URL_KEY, + FEAST_DEFAULT_OPTIONS, +) from feast.core.CoreService_pb2 import ( - GetFeastCoreVersionRequest, - ListFeatureSetsResponse, ApplyFeatureSetRequest, - ListFeatureSetsRequest, ApplyFeatureSetResponse, - GetFeatureSetRequest, - GetFeatureSetResponse, - CreateProjectRequest, - CreateProjectResponse, ArchiveProjectRequest, ArchiveProjectResponse, + CreateProjectRequest, + CreateProjectResponse, + GetFeastCoreVersionRequest, + GetFeatureSetRequest, + GetFeatureSetResponse, + ListFeatureSetsRequest, + ListFeatureSetsResponse, + ListIngestionJobsRequest, ListProjectsRequest, ListProjectsResponse, + RestartIngestionJobRequest, + StopIngestionJobRequest, ) from feast.core.CoreService_pb2_grpc import CoreServiceStub from feast.core.FeatureSet_pb2 import FeatureSetStatus -from feast.feature_set import FeatureSet, Entity -from feast.job import Job -from feast.serving.ServingService_pb2 import FeatureReference +from feast.feature import FeatureRef +from feast.feature_set import Entity, FeatureSet, FeatureSetRef +from feast.job import IngestJob, RetrievalJob from feast.loaders.abstract_producer import get_producer from feast.loaders.file import export_source_to_staging_location -from feast.loaders.ingest import KAFKA_CHUNK_PRODUCTION_TIMEOUT -from feast.loaders.ingest import get_feature_row_chunks -from feast.serving.ServingService_pb2 import GetFeastServingInfoResponse +from feast.loaders.ingest import KAFKA_CHUNK_PRODUCTION_TIMEOUT, get_feature_row_chunks from feast.serving.ServingService_pb2 import ( - GetOnlineFeaturesRequest, + DataFormat, + DatasetSource, + FeastServingType, + FeatureReference, GetBatchFeaturesRequest, GetFeastServingInfoRequest, + GetFeastServingInfoResponse, + GetOnlineFeaturesRequest, GetOnlineFeaturesResponse, - DatasetSource, - DataFormat, - FeastServingType, ) from feast.serving.ServingService_pb2_grpc import ServingServiceStub _logger = logging.getLogger(__name__) -GRPC_CONNECTION_TIMEOUT_DEFAULT = 3 # type: int -GRPC_CONNECTION_TIMEOUT_APPLY = 600 # type: int -FEAST_SERVING_URL_ENV_KEY = "FEAST_SERVING_URL" # type: str -FEAST_CORE_URL_ENV_KEY = "FEAST_CORE_URL" # type: str -FEAST_PROJECT_ENV_KEY = "FEAST_PROJECT" # type: str -BATCH_FEATURE_REQUEST_WAIT_TIME_SECONDS = 300 CPU_COUNT = os.cpu_count() # type: int @@ -81,9 +87,7 @@ class Client: Feast Client: Used for creating, managing, and retrieving features. """ - def __init__( - self, core_url: str = None, serving_url: str = None, project: str = None - ): + def __init__(self, options: Optional[Dict[str, str]] = None, **kwargs): """ The Feast Client should be initialized with at least one service url @@ -91,10 +95,17 @@ def __init__( core_url: Feast Core URL. Used to manage features serving_url: Feast Serving URL. Used to retrieve features project: Sets the active project. This field is optional. + core_secure: Use client-side SSL/TLS for Core gRPC API + serving_secure: Use client-side SSL/TLS for Serving gRPC API + options: Configuration options to initialize client with + **kwargs: Additional keyword arguments that will be used as + configuration options along with "options" """ - self._core_url = core_url - self._serving_url = serving_url - self._project = project + + if options is None: + options = dict() + self._config = Config(options={**options, **kwargs}) + self.__core_channel: grpc.Channel = None self.__serving_channel: grpc.Channel = None self._core_service_stub: CoreServiceStub = None @@ -108,12 +119,7 @@ def core_url(self) -> str: Returns: Feast Core URL string """ - - if self._core_url is not None: - return self._core_url - if os.getenv(FEAST_CORE_URL_ENV_KEY) is not None: - return os.getenv(FEAST_CORE_URL_ENV_KEY) - return "" + return self._config.get(CONFIG_CORE_URL_KEY) @core_url.setter def core_url(self, value: str): @@ -123,7 +129,7 @@ def core_url(self, value: str): Args: value: Feast Core URL """ - self._core_url = value + self._config.set(CONFIG_CORE_URL_KEY, value) @property def serving_url(self) -> str: @@ -133,11 +139,7 @@ def serving_url(self) -> str: Returns: Feast Serving URL string """ - if self._serving_url is not None: - return self._serving_url - if os.getenv(FEAST_SERVING_URL_ENV_KEY) is not None: - return os.getenv(FEAST_SERVING_URL_ENV_KEY) - return "" + return self._config.get(CONFIG_SERVING_URL_KEY) @serving_url.setter def serving_url(self, value: str): @@ -147,7 +149,47 @@ def serving_url(self, value: str): Args: value: Feast Serving URL """ - self._serving_url = value + self._config.set(CONFIG_SERVING_URL_KEY, value) + + @property + def core_secure(self) -> bool: + """ + Retrieve Feast Core client-side SSL/TLS setting + + Returns: + Whether client-side SSL/TLS is enabled + """ + return self._config.getboolean(CONFIG_CORE_SECURE_KEY) + + @core_secure.setter + def core_secure(self, value: bool): + """ + Set the Feast Core client-side SSL/TLS setting + + Args: + value: True to enable client-side SSL/TLS + """ + self._config.set(CONFIG_CORE_SECURE_KEY, value) + + @property + def serving_secure(self) -> bool: + """ + Retrieve Feast Serving client-side SSL/TLS setting + + Returns: + Whether client-side SSL/TLS is enabled + """ + return self._config.getboolean(CONFIG_SERVING_SECURE_KEY) + + @serving_secure.setter + def serving_secure(self, value: bool): + """ + Set the Feast Serving client-side SSL/TLS setting + + Args: + value: True to enable client-side SSL/TLS + """ + self._config.set(CONFIG_SERVING_SECURE_KEY, value) def version(self): """ @@ -158,14 +200,16 @@ def version(self): if self.serving_url: self._connect_serving() serving_version = self._serving_service_stub.GetFeastServingInfo( - GetFeastServingInfoRequest(), timeout=GRPC_CONNECTION_TIMEOUT_DEFAULT + GetFeastServingInfoRequest(), + timeout=self._config.getint(CONFIG_GRPC_CONNECTION_TIMEOUT_DEFAULT_KEY), ).version result["serving"] = {"url": self.serving_url, "version": serving_version} if self.core_url: self._connect_core() core_version = self._core_service_stub.GetFeastCoreVersion( - GetFeastCoreVersionRequest(), timeout=GRPC_CONNECTION_TIMEOUT_DEFAULT + GetFeastCoreVersionRequest(), + timeout=self._config.getint(CONFIG_GRPC_CONNECTION_TIMEOUT_DEFAULT_KEY), ).version result["core"] = {"url": self.core_url, "version": core_version} @@ -185,11 +229,16 @@ def _connect_core(self, skip_if_connected: bool = True): raise ValueError("Please set Feast Core URL.") if self.__core_channel is None: - self.__core_channel = grpc.insecure_channel(self.core_url) + if self.core_secure or self.core_url.endswith(":443"): + self.__core_channel = grpc.secure_channel( + self.core_url, grpc.ssl_channel_credentials() + ) + else: + self.__core_channel = grpc.insecure_channel(self.core_url) try: grpc.channel_ready_future(self.__core_channel).result( - timeout=GRPC_CONNECTION_TIMEOUT_DEFAULT + timeout=self._config.getint(CONFIG_GRPC_CONNECTION_TIMEOUT_DEFAULT_KEY) ) except grpc.FutureTimeoutError: raise ConnectionError( @@ -214,11 +263,16 @@ def _connect_serving(self, skip_if_connected=True): raise ValueError("Please set Feast Serving URL.") if self.__serving_channel is None: - self.__serving_channel = grpc.insecure_channel(self.serving_url) + if self.serving_secure or self.serving_url.endswith(":443"): + self.__serving_channel = grpc.secure_channel( + self.serving_url, grpc.ssl_channel_credentials() + ) + else: + self.__serving_channel = grpc.insecure_channel(self.serving_url) try: grpc.channel_ready_future(self.__serving_channel).result( - timeout=GRPC_CONNECTION_TIMEOUT_DEFAULT + timeout=self._config.getint(CONFIG_GRPC_CONNECTION_TIMEOUT_DEFAULT_KEY) ) except grpc.FutureTimeoutError: raise ConnectionError( @@ -236,20 +290,18 @@ def project(self) -> Union[str, None]: Returns: Project name """ - if self._project is not None: - return self._project - if os.getenv(FEAST_PROJECT_ENV_KEY) is not None: - return os.getenv(FEAST_PROJECT_ENV_KEY) - return None + return self._config.get(CONFIG_PROJECT_KEY) - def set_project(self, project: str): + def set_project(self, project: Optional[str] = None): """ Set currently active Feast project Args: - project: Project to set as active + project: Project to set as active. If unset, will reset to the default project. """ - self._project = project + if project is None: + project = FEAST_DEFAULT_OPTIONS[CONFIG_PROJECT_KEY] + self._config.set(CONFIG_PROJECT_KEY, project) def list_projects(self) -> List[str]: """ @@ -261,7 +313,8 @@ def list_projects(self) -> List[str]: """ self._connect_core() response = self._core_service_stub.ListProjects( - ListProjectsRequest(), timeout=GRPC_CONNECTION_TIMEOUT_DEFAULT + ListProjectsRequest(), + timeout=self._config.getint(CONFIG_GRPC_CONNECTION_TIMEOUT_DEFAULT_KEY), ) # type: ListProjectsResponse return list(response.projects) @@ -275,7 +328,8 @@ def create_project(self, project: str): self._connect_core() self._core_service_stub.CreateProject( - CreateProjectRequest(name=project), timeout=GRPC_CONNECTION_TIMEOUT_DEFAULT + CreateProjectRequest(name=project), + timeout=self._config.getint(CONFIG_GRPC_CONNECTION_TIMEOUT_DEFAULT_KEY), ) # type: CreateProjectResponse def archive_project(self, project): @@ -289,12 +343,17 @@ def archive_project(self, project): """ self._connect_core() - self._core_service_stub.ArchiveProject( - ArchiveProjectRequest(name=project), timeout=GRPC_CONNECTION_TIMEOUT_DEFAULT - ) # type: ArchiveProjectResponse + try: + self._core_service_stub.ArchiveProject( + ArchiveProjectRequest(name=project), + timeout=self._config.getint(CONFIG_GRPC_CONNECTION_TIMEOUT_DEFAULT_KEY), + ) # type: ArchiveProjectResponse + except grpc.RpcError as e: + raise grpc.RpcError(e.details()) + # revert to the default project if self._project == project: - self._project = "" + self._project = FEAST_DEFAULT_OPTIONS[CONFIG_PROJECT_KEY] def apply(self, feature_sets: Union[List[FeatureSet], FeatureSet]): """ @@ -339,7 +398,7 @@ def _apply_feature_set(self, feature_set: FeatureSet): try: apply_fs_response = self._core_service_stub.ApplyFeatureSet( ApplyFeatureSetRequest(feature_set=feature_set_proto), - timeout=GRPC_CONNECTION_TIMEOUT_APPLY, + timeout=self._config.getint(CONFIG_GRPC_CONNECTION_TIMEOUT_DEFAULT_KEY), ) # type: ApplyFeatureSetResponse except grpc.RpcError as e: raise grpc.RpcError(e.details()) @@ -349,9 +408,10 @@ def _apply_feature_set(self, feature_set: FeatureSet): # If the feature set has changed, update the local copy if apply_fs_response.status == ApplyFeatureSetResponse.Status.CREATED: - print( - f'Feature set updated/created: "{applied_fs.name}:{applied_fs.version}"' - ) + print(f'Feature set created: "{applied_fs.name}"') + + if apply_fs_response.status == ApplyFeatureSetResponse.Status.UPDATED: + print(f'Feature set updated: "{applied_fs.name}"') # If no change has been applied, do nothing if apply_fs_response.status == ApplyFeatureSetResponse.Status.NO_CHANGE: @@ -361,7 +421,7 @@ def _apply_feature_set(self, feature_set: FeatureSet): feature_set._update_from_feature_set(applied_fs) def list_feature_sets( - self, project: str = None, name: str = None, version: str = None + self, project: str = None, name: str = None, ) -> List[FeatureSet]: """ Retrieve a list of feature sets from Feast Core @@ -369,7 +429,6 @@ def list_feature_sets( Args: project: Filter feature sets based on project name name: Filter feature sets based on feature set name - version: Filter feature sets based on version number Returns: List of feature sets @@ -385,12 +444,7 @@ def list_feature_sets( if name is None: name = "*" - if version is None: - version = "*" - - filter = ListFeatureSetsRequest.Filter( - project=project, feature_set_name=name, feature_set_version=version - ) + filter = ListFeatureSetsRequest.Filter(project=project, feature_set_name=name) # Get latest feature sets from Feast Core feature_set_protos = self._core_service_stub.ListFeatureSets( @@ -406,16 +460,14 @@ def list_feature_sets( return feature_sets def get_feature_set( - self, name: str, version: int = None, project: str = None + self, name: str, project: str = None ) -> Union[FeatureSet, None]: """ - Retrieves a feature set. If no version is specified then the latest - version will be returned. + Retrieves a feature set. Args: project: Feast project that this feature set belongs to name: Name of feature set - version: Version of feature set Returns: Returns either the specified feature set, or raises an exception if @@ -429,14 +481,9 @@ def get_feature_set( else: raise ValueError("No project has been configured.") - if version is None: - version = 0 - try: get_feature_set_response = self._core_service_stub.GetFeatureSet( - GetFeatureSetRequest( - project=project, name=name.strip(), version=int(version) - ) + GetFeatureSetRequest(project=project, name=name.strip()) ) # type: GetFeatureSetResponse except grpc.RpcError as e: raise grpc.RpcError(e.details()) @@ -459,27 +506,28 @@ def get_batch_features( self, feature_refs: List[str], entity_rows: Union[pd.DataFrame, str], - default_project: str = None, - ) -> Job: + project: str = None, + ) -> RetrievalJob: """ Retrieves historical features from a Feast Serving deployment. Args: - feature_refs (List[str]): - List of feature references that will be returned for each entity. - Each feature reference should have the following format - "project/feature:version". - + feature_refs: List of feature references that will be returned for each entity. + Each feature reference should have the following format: + "feature_set:feature" where "feature_set" & "feature" refer to + the feature and feature set names respectively. + Only the feature name is required. entity_rows (Union[pd.DataFrame, str]): Pandas dataframe containing entities and a 'datetime' column. Each entity in a feature set must be present as a column in this dataframe. The datetime column must contain timestamps in datetime64 format. - default_project: Default project where feature values will be found. + project: Specifies the project which contain the FeatureSets + which the requested features belong to. Returns: - feast.job.Job: - Returns a job object that can be used to monitor retrieval + feast.job.RetrievalJob: + Returns a retrival job object that can be used to monitor retrieval progress asynchronously, and can be used to materialize the results. @@ -488,7 +536,7 @@ def get_batch_features( >>> from datetime import datetime >>> >>> feast_client = Client(core_url="localhost:6565", serving_url="localhost:6566") - >>> feature_refs = ["my_project/bookings_7d:1", "booking_14d"] + >>> feature_refs = ["my_project/bookings_7d", "booking_14d"] >>> entity_rows = pd.DataFrame( >>> { >>> "datetime": [pd.datetime.now() for _ in range(3)], @@ -503,20 +551,17 @@ def get_batch_features( self._connect_serving() - feature_references = _build_feature_references( - feature_refs=feature_refs, default_project=default_project - ) - # Retrieve serving information to determine store type and # staging location serving_info = self._serving_service_stub.GetFeastServingInfo( - GetFeastServingInfoRequest(), timeout=GRPC_CONNECTION_TIMEOUT_DEFAULT + GetFeastServingInfoRequest(), + timeout=self._config.getint(CONFIG_GRPC_CONNECTION_TIMEOUT_DEFAULT_KEY), ) # type: GetFeastServingInfoResponse if serving_info.type != FeastServingType.FEAST_SERVING_TYPE_BATCH: raise Exception( - f'You are connected to a store "{self._serving_url}" which ' - f"does not support batch retrieval " + f'You are connected to a store "{self.serving_url}" which ' + f"does not support batch retrieval" ) if isinstance(entity_rows, pd.DataFrame): @@ -533,7 +578,7 @@ def get_batch_features( # String based source if not entity_rows.endswith((".avro", "*")): raise Exception( - f"Only .avro and wildcard paths are accepted as entity_rows" + "Only .avro and wildcard paths are accepted as entity_rows" ) else: raise Exception( @@ -546,9 +591,11 @@ def get_batch_features( staged_files = export_source_to_staging_location( entity_rows, serving_info.job_staging_location ) # type: List[str] - request = GetBatchFeaturesRequest( - features=feature_references, + features=_build_feature_references( + feature_ref_strs=feature_refs, + project=project if project is not None else self.project, + ), dataset_source=DatasetSource( file_source=DatasetSource.FileSource( file_uris=staged_files, data_format=DataFormat.DATA_FORMAT_AVRO @@ -557,30 +604,33 @@ def get_batch_features( ) # Retrieve Feast Job object to manage life cycle of retrieval - response = self._serving_service_stub.GetBatchFeatures(request) - return Job(response.job, self._serving_service_stub) + try: + response = self._serving_service_stub.GetBatchFeatures(request) + except grpc.RpcError as e: + raise grpc.RpcError(e.details()) + + return RetrievalJob(response.job, self._serving_service_stub) def get_online_features( self, feature_refs: List[str], entity_rows: List[GetOnlineFeaturesRequest.EntityRow], - default_project: Optional[str] = None, + project: Optional[str] = None, ) -> GetOnlineFeaturesResponse: """ Retrieves the latest online feature data from Feast Serving Args: - feature_refs: List of feature references in the following format - [project]/[feature_name]:[version]. Only the feature name - is a required component in the reference. - example: - ["my_project/my_feature_1:3", - "my_project3/my_feature_4:1",] + feature_refs: List of feature references that will be returned for each entity. + Each feature reference should have the following format: + "feature_set:feature" where "feature_set" & "feature" refer to + the feature and feature set names respectively. + Only the feature name is required. entity_rows: List of GetFeaturesRequest.EntityRow where each row contains entities. Timestamp should not be set for online retrieval. All entity types within a feature - default_project: This project will be used if the project name is - not provided in the feature reference + project: Specifies the project which contain the FeatureSets + which the requested features belong to. Returns: Returns a list of maps where each item in the list contains the @@ -588,36 +638,127 @@ def get_online_features( """ self._connect_serving() - return self._serving_service_stub.GetOnlineFeatures( - GetOnlineFeaturesRequest( - features=_build_feature_references( - feature_refs=feature_refs, - default_project=( - default_project if not self.project else self.project + try: + response = self._serving_service_stub.GetOnlineFeatures( + GetOnlineFeaturesRequest( + features=_build_feature_references( + feature_ref_strs=feature_refs, + project=project if project is not None else self.project, ), - ), - entity_rows=entity_rows, + entity_rows=entity_rows, + ) ) - ) # type: GetOnlineFeaturesResponse + # collect entity row refs + entity_refs = set() + for entity_row in entity_rows: + entity_refs.update(entity_row.fields.keys()) + + strip_field_values = [] + for field_value in response.field_values: + # strip the project part the string feature references returned from serving + strip_fields = {} + for ref_str, value in field_value.fields.items(): + # find and ignore entities + if ref_str in entity_refs: + strip_fields[ref_str] = value + else: + strip_ref_str = repr( + FeatureRef.from_str(ref_str, ignore_project=True) + ) + strip_fields[strip_ref_str] = value + strip_field_values.append( + GetOnlineFeaturesResponse.FieldValues(fields=strip_fields) + ) + + del response.field_values[:] + response.field_values.extend(strip_field_values) + + except grpc.RpcError as e: + raise grpc.RpcError(e.details()) + + return response + + def list_ingest_jobs( + self, + job_id: str = None, + feature_set_ref: FeatureSetRef = None, + store_name: str = None, + ): + """ + List the ingestion jobs currently registered in Feast, with optional filters. + Provides detailed metadata about each ingestion job. + + Args: + job_id: Select specific ingestion job with the given job_id + feature_set_ref: Filter ingestion jobs by target feature set (via reference) + store_name: Filter ingestion jobs by target feast store's name + + Returns: + List of IngestJobs matching the given filters + """ + self._connect_core() + # construct list request + feature_set_ref = None + list_filter = ListIngestionJobsRequest.Filter( + id=job_id, feature_set_reference=feature_set_ref, store_name=store_name, + ) + request = ListIngestionJobsRequest(filter=list_filter) + # make list request & unpack response + response = self._core_service_stub.ListIngestionJobs(request) + ingest_jobs = [ + IngestJob(proto, self._core_service_stub) for proto in response.jobs + ] + return ingest_jobs + + def restart_ingest_job(self, job: IngestJob): + """ + Restart ingestion job currently registered in Feast. + NOTE: Data might be lost during the restart for some job runners. + Does not support stopping a job in a transitional (ie pending, suspending, aborting), + terminal state (ie suspended or aborted) or unknown status + + Args: + job: IngestJob to restart + """ + self._connect_core() + request = RestartIngestionJobRequest(id=job.id) + try: + self._core_service_stub.RestartIngestionJob(request) + except grpc.RpcError as e: + raise grpc.RpcError(e.details()) + + def stop_ingest_job(self, job: IngestJob): + """ + Stop ingestion job currently resgistered in Feast + Does nothing if the target job if already in a terminal state (ie suspended or aborted). + Does not support stopping a job in a transitional (ie pending, suspending, aborting) + or in a unknown status + + Args: + job: IngestJob to restart + """ + self._connect_core() + request = StopIngestionJobRequest(id=job.id) + try: + self._core_service_stub.StopIngestionJob(request) + except grpc.RpcError as e: + raise grpc.RpcError(e.details()) def ingest( self, feature_set: Union[str, FeatureSet], source: Union[pd.DataFrame, str], chunk_size: int = 10000, - version: int = None, - force_update: bool = False, max_workers: int = max(CPU_COUNT - 1, 1), disable_progress_bar: bool = False, timeout: int = KAFKA_CHUNK_PRODUCTION_TIMEOUT, - ) -> None: + ) -> str: """ Loads feature data into Feast for a specific feature set. Args: - feature_set (typing.Union[str, FeatureSet]): + feature_set (typing.Union[str, feast.feature_set.FeatureSet]): Feature set object or the string name of the feature set - (without a version). source (typing.Union[pd.DataFrame, str]): Either a file path or Pandas Dataframe to ingest into Feast @@ -629,13 +770,6 @@ def ingest( chunk_size (int): Amount of rows to load and ingest at a time. - version (int): - Feature set version. - - force_update (bool): - Automatically update feature set based on source data prior to - ingesting. This will also register changes to Feast. - max_workers (int): Number of worker processes to use to encode values. @@ -646,18 +780,16 @@ def ingest( Timeout in seconds to wait for completion. Returns: - None: - None + str: + ingestion id for this dataset """ if isinstance(feature_set, FeatureSet): name = feature_set.name - if version is None: - version = feature_set.version elif isinstance(feature_set, str): name = feature_set else: - raise Exception(f"Feature set name must be provided") + raise Exception("Feature set name must be provided") # Read table and get row count dir_path, dest_path = _read_table_from_source(source, chunk_size, max_workers) @@ -666,21 +798,13 @@ def ingest( row_count = pq_file.metadata.num_rows - # Update the feature set based on PyArrow table of first row group - if force_update: - feature_set.infer_fields_from_pa( - table=pq_file.read_row_group(0), - discard_unused_fields=True, - replace_existing_features=True, - ) - self.apply(feature_set) current_time = time.time() print("Waiting for feature set to be ready for ingestion...") while True: if timeout is not None and time.time() - current_time >= timeout: raise TimeoutError("Timed out waiting for feature set to be ready") - feature_set = self.get_feature_set(name, version) + feature_set = self.get_feature_set(name) if ( feature_set is not None and feature_set.status == FeatureSetStatus.STATUS_READY @@ -700,6 +824,7 @@ def ingest( # Loop optimization declarations produce = producer.produce flush = producer.flush + ingestion_id = _generate_ingestion_id(feature_set) # Transform and push data to Kafka if feature_set.source.source_type == "Kafka": @@ -707,6 +832,7 @@ def ingest( file=dest_path, row_groups=list(range(pq_file.num_row_groups)), fs=feature_set, + ingestion_id=ingestion_id, max_workers=max_workers, ): @@ -734,61 +860,43 @@ def ingest( print("Removing temporary file(s)...") shutil.rmtree(dir_path) - return None + return ingestion_id def _build_feature_references( - feature_refs: List[str], default_project: str = None + feature_ref_strs: List[str], project: Optional[str] = None ) -> List[FeatureReference]: """ - Builds a list of FeatureSet objects from feature set ids in order to - retrieve feature data from Feast Serving + Builds a list of FeatureReference protos from string feature set references Args: - feature_refs: List of feature reference strings - ("project/feature:version") - default_project: This project will be used if the project name is - not provided in the feature reference - """ - - features = [] + feature_ref_strs: List of string feature references + project: Optionally specifies the project in the parsed feature references. - for feature_ref in feature_refs: - project_split = feature_ref.split("/") - version = 0 + Returns: + A list of FeatureReference protos parsed from args. + """ + feature_refs = [FeatureRef.from_str(ref_str) for ref_str in feature_ref_strs] + feature_ref_protos = [ref.to_proto() for ref in feature_refs] + # apply project if specified + if project is not None: + for feature_ref_proto in feature_ref_protos: + feature_ref_proto.project = project + return feature_ref_protos - if len(project_split) == 2: - project, feature_version = project_split - elif len(project_split) == 1: - feature_version = project_split[0] - if default_project is None: - raise ValueError( - f"No project specified in {feature_ref} and no default project provided" - ) - project = default_project - else: - raise ValueError( - f'Could not parse feature ref {feature_ref}, expecting "project/feature:version"' - ) - feature_split = feature_version.split(":") - if len(feature_split) == 2: - name, version = feature_split - version = int(version) - elif len(feature_split) == 1: - name = feature_split[0] - else: - raise ValueError( - f'Could not parse feature ref {feature_ref}, expecting "project/feature:version"' - ) +def _generate_ingestion_id(feature_set: FeatureSet) -> str: + """ + Generates a UUID from the feature set name, version, and the current time. - if len(project) == 0 or len(name) == 0 or version < 0: - raise ValueError( - f'Could not parse feature ref {feature_ref}, expecting "project/feature:version"' - ) + Args: + feature_set: Feature set of the dataset to be ingested. - features.append(FeatureReference(project=project, name=name, version=version)) - return features + Returns: + UUID unique to current time and the feature set provided. + """ + uuid_str = f"{feature_set.name}_{int(time.time())}" + return str(uuid.uuid3(uuid.NAMESPACE_DNS, uuid_str)) def _read_table_from_source( diff --git a/sdk/python/feast/config.py b/sdk/python/feast/config.py index 130b76cc86a..fd35b6e5d87 100644 --- a/sdk/python/feast/config.py +++ b/sdk/python/feast/config.py @@ -13,153 +13,212 @@ # See the License for the specific language governing permissions and # limitations under the License. # - -from os.path import expanduser, join import logging import os -import sys -from typing import Dict -from urllib.parse import urlparse -from urllib.parse import ParseResult +from configparser import ConfigParser, NoOptionError +from os.path import expanduser, join +from typing import Dict, Optional -import toml +from feast.constants import ( + CONFIG_FEAST_ENV_VAR_PREFIX, + CONFIG_FILE_DEFAULT_DIRECTORY, + CONFIG_FILE_NAME, + CONFIG_FILE_SECTION, + FEAST_CONFIG_FILE_ENV_KEY, +) +from feast.constants import FEAST_DEFAULT_OPTIONS as DEFAULTS _logger = logging.getLogger(__name__) -feast_configuration_properties = {"core_url": "URL", "serving_url": "URL"} -CONFIGURATION_FILE_DIR = os.environ.get("FEAST_CONFIG", ".feast") -CONFIGURATION_FILE_NAME = "config.toml" +def _init_config(path: str): + """ + Returns a ConfigParser that reads in a feast configuration file. If the + file does not exist it will be created. + Args: + path: Optional path to initialize as Feast configuration -def _get_or_create_config() -> Dict: - """Get user configuration file or create it and return""" + Returns: ConfigParser of the Feast configuration file, with defaults + preloaded - user_config_file_dir, user_config_file_path = _get_config_file_locations() - user_config_file_dir = user_config_file_dir.rstrip("/") + "/" - if not os.path.exists(os.path.dirname(user_config_file_dir)): - os.makedirs(os.path.dirname(user_config_file_dir)) + """ + # Create the configuration file directory if needed + config_dir = os.path.dirname(path) + config_dir = config_dir.rstrip("/") + "/" - if not os.path.isfile(user_config_file_path): - _save_config(user_config_file_path, _props_to_dict()) + if not os.path.exists(os.path.dirname(config_dir)): + os.makedirs(os.path.dirname(config_dir)) - try: - return toml.load(user_config_file_path) - except FileNotFoundError: - _logger.error( - "Could not find Feast configuration file " + user_config_file_path - ) - sys.exit(1) - except toml.decoder.TomlDecodeError: - _logger.error( - "Could not decode Feast configuration file " + user_config_file_path - ) - sys.exit(1) - except Exception as e: - _logger.error(e) - sys.exit(1) + # Create the configuration file itself + config = ConfigParser(defaults=DEFAULTS) + if os.path.exists(path): + config.read(path) + # Store all configuration in a single section + if not config.has_section(CONFIG_FILE_SECTION): + config.add_section(CONFIG_FILE_SECTION) -def set_property(prop: str, value: str): - """ - Sets a single property in the Feast users local configuration file + # Save the current configuration + config.write(open(path, "w")) - Args: - prop: Feast property name - value: Feast property value + return config + + +def _get_feast_env_vars(): """ - if _is_valid_property(prop, value): - active_feast_config = _get_or_create_config() - active_feast_config[prop] = value - _, user_config_file_path = _get_config_file_locations() - _save_config(user_config_file_path, active_feast_config) - print("Updated property [%s]" % prop) - else: - _logger.error("Invalid property selected") - sys.exit(1) - - -def get_config_property_or_fail(prop: str, force_config: Dict[str, str] = None) -> str: + Get environmental variables that start with FEAST_ + Returns: Dict of Feast environmental variables (stripped of prefix) """ - Gets a single property in the users configuration + feast_env_vars = {} + for key in os.environ.keys(): + if key.upper().startswith(CONFIG_FEAST_ENV_VAR_PREFIX): + feast_env_vars[key[len(CONFIG_FEAST_ENV_VAR_PREFIX) :]] = os.environ[key] + return feast_env_vars - Args: - prop: Property to retrieve - force_config: Configuration dictionary containing properties that should - be overridden. This will take precedence over file based properties. - Returns: - Returns a string property +class Config: """ - if ( - isinstance(force_config, dict) - and prop in force_config - and force_config[prop] is not None + Maintains and provides access to Feast configuration + + Configuration is stored as key/value pairs. The user can specify options + through either input arguments to this class, environmental variables, or + by setting the config in a configuration file + + """ + + def __init__( + self, options: Optional[Dict[str, str]] = None, path: Optional[str] = None, ): - return force_config[prop] + """ + Configuration options are returned as follows (higher replaces lower) + 1. Initialized options ("options" argument) + 2. Environmental variables (reloaded on every "get") + 3. Configuration file options (loaded once) + 4. Default options (loaded once from memory) + + Args: + options: (optional) A list of initialized/hardcoded options. + path: (optional) File path to configuration file + """ + if not path: + path = join( + expanduser("~"), + os.environ.get( + FEAST_CONFIG_FILE_ENV_KEY, CONFIG_FILE_DEFAULT_DIRECTORY, + ), + CONFIG_FILE_NAME, + ) + + config = _init_config(path) + + self._options = {} + if options and isinstance(options, dict): + self._options = options + + self._config = config # type: ConfigParser + self._path = path # type: str + + def get(self, option): + """ + Returns a single configuration option as a string + + Args: + option: Name of the option + + Returns: String option that is returned + + """ + return self._config.get( + CONFIG_FILE_SECTION, + option, + vars={**_get_feast_env_vars(), **self._options}, + ) - active_feast_config = _get_or_create_config() - if _is_valid_property(prop, active_feast_config[prop]): - return active_feast_config[prop] - _logger.error("Could not load Feast property from configuration: %s" % prop) - sys.exit(1) + def getboolean(self, option): + """ + Returns a single configuration option as a boolean + Args: + option: Name of the option -def _props_to_dict() -> Dict[str, str]: - """Create empty dictionary of all Feast properties""" - prop_dict = {} - for prop in feast_configuration_properties: - prop_dict[prop] = "" - return prop_dict + Returns: Boolean option value that is returned + """ + return self._config.getboolean( + CONFIG_FILE_SECTION, + option, + vars={**_get_feast_env_vars(), **self._options}, + ) -def _is_valid_property(prop: str, value: str) -> bool: - """ - Validates both a Feast property as well as value + def getint(self, option): + """ + Returns a single configuration option as an integer - Args: - prop: Feast property name - value: Feast property value + Args: + option: Name of the option - Returns: - Returns True if property and value are valid - """ - if prop not in feast_configuration_properties: - _logger.error("You are trying to set an invalid property") - sys.exit(1) + Returns: Integer option value that is returned - prop_type = feast_configuration_properties[prop] + """ + return self._config.getint( + CONFIG_FILE_SECTION, + option, + vars={**_get_feast_env_vars(), **self._options}, + ) - if prop_type == "URL": - if "//" not in value: - value = "%s%s" % ("grpc://", value) - parsed_value = urlparse(value) # type: ParseResult - if parsed_value.netloc: - return True + def getfloat(self, option): + """ + Returns a single configuration option as an integer - _logger.error("The property you are trying to set could not be identified") - sys.exit(1) + Args: + option: Name of the option + Returns: Float option value that is returned -def _save_config(user_config_file_path: str, config_string: Dict[str, str]): - """ - Saves Feast configuration + """ + return self._config.getfloat( + CONFIG_FILE_SECTION, + option, + vars={**_get_feast_env_vars(), **self._options}, + ) - Args: - user_config_file_path: Local file system path to save configuration - config_string: Contents in dictionary format to save to path - """ - try: - with open(user_config_file_path, "w+") as f: - toml.dump(config_string, f) - except Exception as e: - _logger.error("Could not update configuration file for Feast") - print(e) - sys.exit(1) - - -def _get_config_file_locations() -> (str, str): - """Gets the local user configuration directory and file path""" - user_config_file_dir = join(expanduser("~"), CONFIGURATION_FILE_DIR) - user_config_file_path = join(user_config_file_dir, CONFIGURATION_FILE_NAME) - return user_config_file_dir, user_config_file_path + def set(self, option, value): + """ + Sets a configuration option. Must be serializable to string + Args: + option: Option name to use as key + value: Value to store under option + """ + self._config.set(CONFIG_FILE_SECTION, option, value=str(value)) + + def exists(self, option): + """ + Tests whether a specific option is available + + Args: + option: Name of the option to check + + Returns: Boolean true/false whether the option is set + + """ + try: + self.get(option=option) + return True + except NoOptionError: + return False + + def save(self): + """ + Save the current configuration to disk. This does not include + environmental variables or initialized options + """ + self._config.write(open(self._path, "w")) + + def __str__(self): + result = "" + for section_name in self._config.sections(): + result += "\n[" + section_name + "]\n" + for name, value in self._config.items(section_name): + result += name + " = " + value + "\n" + return result diff --git a/sdk/python/feast/constants.py b/sdk/python/feast/constants.py index 9b001ac4067..c4bde75404a 100644 --- a/sdk/python/feast/constants.py +++ b/sdk/python/feast/constants.py @@ -14,4 +14,35 @@ # limitations under the License. # -DATETIME_COLUMN = "datetime" # type: str +# General constants +DATETIME_COLUMN = "datetime" +FEAST_CONFIG_FILE_ENV_KEY = "FEAST_CONFIG" +CONFIG_FEAST_ENV_VAR_PREFIX = "FEAST_" +CONFIG_FILE_DEFAULT_DIRECTORY = ".feast" +CONFIG_FILE_NAME = "config" +CONFIG_FILE_SECTION = "general" + + +# Feast configuration options +CONFIG_CORE_URL_KEY = "core_url" +CONFIG_SERVING_URL_KEY = "serving_url" +CONFIG_PROJECT_KEY = "project" +CONFIG_CORE_SECURE_KEY = "core_secure" +CONFIG_SERVING_SECURE_KEY = "serving_secure" +CONFIG_GRPC_CONNECTION_TIMEOUT_DEFAULT_KEY = "grpc_connection_timeout_default" +CONFIG_GRPC_CONNECTION_TIMEOUT_APPLY_KEY = "grpc_connection_timeout_apply_key" +CONFIG_BATCH_FEATURE_REQUEST_WAIT_TIME_SECONDS_KEY = ( + "batch_feature_request_wait_time_seconds" +) + +# Configuration option default values +FEAST_DEFAULT_OPTIONS = { + CONFIG_PROJECT_KEY: "default", + CONFIG_CORE_URL_KEY: "localhost:6565", + CONFIG_CORE_SECURE_KEY: "False", + CONFIG_SERVING_URL_KEY: "localhost:6565", + CONFIG_SERVING_SECURE_KEY: "False", + CONFIG_GRPC_CONNECTION_TIMEOUT_DEFAULT_KEY: "3", + CONFIG_GRPC_CONNECTION_TIMEOUT_APPLY_KEY: "600", + CONFIG_BATCH_FEATURE_REQUEST_WAIT_TIME_SECONDS_KEY: "600", +} diff --git a/sdk/python/feast/core/CoreService_pb2.py b/sdk/python/feast/core/CoreService_pb2.py deleted file mode 100644 index 858703d7f3e..00000000000 --- a/sdk/python/feast/core/CoreService_pb2.py +++ /dev/null @@ -1,988 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: feast/core/CoreService.proto - -import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from feast.core import FeatureSet_pb2 as feast_dot_core_dot_FeatureSet__pb2 -from feast.core import Store_pb2 as feast_dot_core_dot_Store__pb2 - - -DESCRIPTOR = _descriptor.FileDescriptor( - name='feast/core/CoreService.proto', - package='feast.core', - syntax='proto3', - serialized_options=_b('\n\nfeast.coreB\020CoreServiceProtoZ/github.com/gojek/feast/sdk/go/protos/feast/core'), - serialized_pb=_b('\n\x1c\x66\x65\x61st/core/CoreService.proto\x12\nfeast.core\x1a\x1b\x66\x65\x61st/core/FeatureSet.proto\x1a\x16\x66\x65\x61st/core/Store.proto\"F\n\x14GetFeatureSetRequest\x12\x0f\n\x07project\x18\x03 \x01(\t\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\x05\"D\n\x15GetFeatureSetResponse\x12+\n\x0b\x66\x65\x61ture_set\x18\x01 \x01(\x0b\x32\x16.feast.core.FeatureSet\"\xa5\x01\n\x16ListFeatureSetsRequest\x12\x39\n\x06\x66ilter\x18\x01 \x01(\x0b\x32).feast.core.ListFeatureSetsRequest.Filter\x1aP\n\x06\x46ilter\x12\x0f\n\x07project\x18\x03 \x01(\t\x12\x18\n\x10\x66\x65\x61ture_set_name\x18\x01 \x01(\t\x12\x1b\n\x13\x66\x65\x61ture_set_version\x18\x02 \x01(\t\"G\n\x17ListFeatureSetsResponse\x12,\n\x0c\x66\x65\x61ture_sets\x18\x01 \x03(\x0b\x32\x16.feast.core.FeatureSet\"a\n\x11ListStoresRequest\x12\x34\n\x06\x66ilter\x18\x01 \x01(\x0b\x32$.feast.core.ListStoresRequest.Filter\x1a\x16\n\x06\x46ilter\x12\x0c\n\x04name\x18\x01 \x01(\t\"6\n\x12ListStoresResponse\x12 \n\x05store\x18\x01 \x03(\x0b\x32\x11.feast.core.Store\"E\n\x16\x41pplyFeatureSetRequest\x12+\n\x0b\x66\x65\x61ture_set\x18\x01 \x01(\x0b\x32\x16.feast.core.FeatureSet\"\xb3\x01\n\x17\x41pplyFeatureSetResponse\x12+\n\x0b\x66\x65\x61ture_set\x18\x01 \x01(\x0b\x32\x16.feast.core.FeatureSet\x12:\n\x06status\x18\x02 \x01(\x0e\x32*.feast.core.ApplyFeatureSetResponse.Status\"/\n\x06Status\x12\r\n\tNO_CHANGE\x10\x00\x12\x0b\n\x07\x43REATED\x10\x01\x12\t\n\x05\x45RROR\x10\x02\"\x1c\n\x1aGetFeastCoreVersionRequest\".\n\x1bGetFeastCoreVersionResponse\x12\x0f\n\x07version\x18\x01 \x01(\t\"6\n\x12UpdateStoreRequest\x12 \n\x05store\x18\x01 \x01(\x0b\x32\x11.feast.core.Store\"\x95\x01\n\x13UpdateStoreResponse\x12 \n\x05store\x18\x01 \x01(\x0b\x32\x11.feast.core.Store\x12\x36\n\x06status\x18\x02 \x01(\x0e\x32&.feast.core.UpdateStoreResponse.Status\"$\n\x06Status\x12\r\n\tNO_CHANGE\x10\x00\x12\x0b\n\x07UPDATED\x10\x01\"$\n\x14\x43reateProjectRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"\x17\n\x15\x43reateProjectResponse\"%\n\x15\x41rchiveProjectRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"\x18\n\x16\x41rchiveProjectResponse\"\x15\n\x13ListProjectsRequest\"(\n\x14ListProjectsResponse\x12\x10\n\x08projects\x18\x01 \x03(\t2\xa2\x06\n\x0b\x43oreService\x12\x66\n\x13GetFeastCoreVersion\x12&.feast.core.GetFeastCoreVersionRequest\x1a\'.feast.core.GetFeastCoreVersionResponse\x12T\n\rGetFeatureSet\x12 .feast.core.GetFeatureSetRequest\x1a!.feast.core.GetFeatureSetResponse\x12Z\n\x0fListFeatureSets\x12\".feast.core.ListFeatureSetsRequest\x1a#.feast.core.ListFeatureSetsResponse\x12K\n\nListStores\x12\x1d.feast.core.ListStoresRequest\x1a\x1e.feast.core.ListStoresResponse\x12Z\n\x0f\x41pplyFeatureSet\x12\".feast.core.ApplyFeatureSetRequest\x1a#.feast.core.ApplyFeatureSetResponse\x12N\n\x0bUpdateStore\x12\x1e.feast.core.UpdateStoreRequest\x1a\x1f.feast.core.UpdateStoreResponse\x12T\n\rCreateProject\x12 .feast.core.CreateProjectRequest\x1a!.feast.core.CreateProjectResponse\x12W\n\x0e\x41rchiveProject\x12!.feast.core.ArchiveProjectRequest\x1a\".feast.core.ArchiveProjectResponse\x12Q\n\x0cListProjects\x12\x1f.feast.core.ListProjectsRequest\x1a .feast.core.ListProjectsResponseBO\n\nfeast.coreB\x10\x43oreServiceProtoZ/github.com/gojek/feast/sdk/go/protos/feast/coreb\x06proto3') - , - dependencies=[feast_dot_core_dot_FeatureSet__pb2.DESCRIPTOR,feast_dot_core_dot_Store__pb2.DESCRIPTOR,]) - - - -_APPLYFEATURESETRESPONSE_STATUS = _descriptor.EnumDescriptor( - name='Status', - full_name='feast.core.ApplyFeatureSetResponse.Status', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='NO_CHANGE', index=0, number=0, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='CREATED', index=1, number=1, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='ERROR', index=2, number=2, - serialized_options=None, - type=None), - ], - containing_type=None, - serialized_options=None, - serialized_start=839, - serialized_end=886, -) -_sym_db.RegisterEnumDescriptor(_APPLYFEATURESETRESPONSE_STATUS) - -_UPDATESTORERESPONSE_STATUS = _descriptor.EnumDescriptor( - name='Status', - full_name='feast.core.UpdateStoreResponse.Status', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='NO_CHANGE', index=0, number=0, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='UPDATED', index=1, number=1, - serialized_options=None, - type=None), - ], - containing_type=None, - serialized_options=None, - serialized_start=1136, - serialized_end=1172, -) -_sym_db.RegisterEnumDescriptor(_UPDATESTORERESPONSE_STATUS) - - -_GETFEATURESETREQUEST = _descriptor.Descriptor( - name='GetFeatureSetRequest', - full_name='feast.core.GetFeatureSetRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='project', full_name='feast.core.GetFeatureSetRequest.project', index=0, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='name', full_name='feast.core.GetFeatureSetRequest.name', index=1, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='version', full_name='feast.core.GetFeatureSetRequest.version', index=2, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=97, - serialized_end=167, -) - - -_GETFEATURESETRESPONSE = _descriptor.Descriptor( - name='GetFeatureSetResponse', - full_name='feast.core.GetFeatureSetResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='feature_set', full_name='feast.core.GetFeatureSetResponse.feature_set', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=169, - serialized_end=237, -) - - -_LISTFEATURESETSREQUEST_FILTER = _descriptor.Descriptor( - name='Filter', - full_name='feast.core.ListFeatureSetsRequest.Filter', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='project', full_name='feast.core.ListFeatureSetsRequest.Filter.project', index=0, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='feature_set_name', full_name='feast.core.ListFeatureSetsRequest.Filter.feature_set_name', index=1, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='feature_set_version', full_name='feast.core.ListFeatureSetsRequest.Filter.feature_set_version', index=2, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=325, - serialized_end=405, -) - -_LISTFEATURESETSREQUEST = _descriptor.Descriptor( - name='ListFeatureSetsRequest', - full_name='feast.core.ListFeatureSetsRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='filter', full_name='feast.core.ListFeatureSetsRequest.filter', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[_LISTFEATURESETSREQUEST_FILTER, ], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=240, - serialized_end=405, -) - - -_LISTFEATURESETSRESPONSE = _descriptor.Descriptor( - name='ListFeatureSetsResponse', - full_name='feast.core.ListFeatureSetsResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='feature_sets', full_name='feast.core.ListFeatureSetsResponse.feature_sets', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=407, - serialized_end=478, -) - - -_LISTSTORESREQUEST_FILTER = _descriptor.Descriptor( - name='Filter', - full_name='feast.core.ListStoresRequest.Filter', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='name', full_name='feast.core.ListStoresRequest.Filter.name', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=555, - serialized_end=577, -) - -_LISTSTORESREQUEST = _descriptor.Descriptor( - name='ListStoresRequest', - full_name='feast.core.ListStoresRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='filter', full_name='feast.core.ListStoresRequest.filter', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[_LISTSTORESREQUEST_FILTER, ], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=480, - serialized_end=577, -) - - -_LISTSTORESRESPONSE = _descriptor.Descriptor( - name='ListStoresResponse', - full_name='feast.core.ListStoresResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='store', full_name='feast.core.ListStoresResponse.store', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=579, - serialized_end=633, -) - - -_APPLYFEATURESETREQUEST = _descriptor.Descriptor( - name='ApplyFeatureSetRequest', - full_name='feast.core.ApplyFeatureSetRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='feature_set', full_name='feast.core.ApplyFeatureSetRequest.feature_set', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=635, - serialized_end=704, -) - - -_APPLYFEATURESETRESPONSE = _descriptor.Descriptor( - name='ApplyFeatureSetResponse', - full_name='feast.core.ApplyFeatureSetResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='feature_set', full_name='feast.core.ApplyFeatureSetResponse.feature_set', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='status', full_name='feast.core.ApplyFeatureSetResponse.status', index=1, - number=2, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - _APPLYFEATURESETRESPONSE_STATUS, - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=707, - serialized_end=886, -) - - -_GETFEASTCOREVERSIONREQUEST = _descriptor.Descriptor( - name='GetFeastCoreVersionRequest', - full_name='feast.core.GetFeastCoreVersionRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=888, - serialized_end=916, -) - - -_GETFEASTCOREVERSIONRESPONSE = _descriptor.Descriptor( - name='GetFeastCoreVersionResponse', - full_name='feast.core.GetFeastCoreVersionResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='version', full_name='feast.core.GetFeastCoreVersionResponse.version', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=918, - serialized_end=964, -) - - -_UPDATESTOREREQUEST = _descriptor.Descriptor( - name='UpdateStoreRequest', - full_name='feast.core.UpdateStoreRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='store', full_name='feast.core.UpdateStoreRequest.store', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=966, - serialized_end=1020, -) - - -_UPDATESTORERESPONSE = _descriptor.Descriptor( - name='UpdateStoreResponse', - full_name='feast.core.UpdateStoreResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='store', full_name='feast.core.UpdateStoreResponse.store', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='status', full_name='feast.core.UpdateStoreResponse.status', index=1, - number=2, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - _UPDATESTORERESPONSE_STATUS, - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1023, - serialized_end=1172, -) - - -_CREATEPROJECTREQUEST = _descriptor.Descriptor( - name='CreateProjectRequest', - full_name='feast.core.CreateProjectRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='name', full_name='feast.core.CreateProjectRequest.name', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1174, - serialized_end=1210, -) - - -_CREATEPROJECTRESPONSE = _descriptor.Descriptor( - name='CreateProjectResponse', - full_name='feast.core.CreateProjectResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1212, - serialized_end=1235, -) - - -_ARCHIVEPROJECTREQUEST = _descriptor.Descriptor( - name='ArchiveProjectRequest', - full_name='feast.core.ArchiveProjectRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='name', full_name='feast.core.ArchiveProjectRequest.name', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1237, - serialized_end=1274, -) - - -_ARCHIVEPROJECTRESPONSE = _descriptor.Descriptor( - name='ArchiveProjectResponse', - full_name='feast.core.ArchiveProjectResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1276, - serialized_end=1300, -) - - -_LISTPROJECTSREQUEST = _descriptor.Descriptor( - name='ListProjectsRequest', - full_name='feast.core.ListProjectsRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1302, - serialized_end=1323, -) - - -_LISTPROJECTSRESPONSE = _descriptor.Descriptor( - name='ListProjectsResponse', - full_name='feast.core.ListProjectsResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='projects', full_name='feast.core.ListProjectsResponse.projects', index=0, - number=1, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1325, - serialized_end=1365, -) - -_GETFEATURESETRESPONSE.fields_by_name['feature_set'].message_type = feast_dot_core_dot_FeatureSet__pb2._FEATURESET -_LISTFEATURESETSREQUEST_FILTER.containing_type = _LISTFEATURESETSREQUEST -_LISTFEATURESETSREQUEST.fields_by_name['filter'].message_type = _LISTFEATURESETSREQUEST_FILTER -_LISTFEATURESETSRESPONSE.fields_by_name['feature_sets'].message_type = feast_dot_core_dot_FeatureSet__pb2._FEATURESET -_LISTSTORESREQUEST_FILTER.containing_type = _LISTSTORESREQUEST -_LISTSTORESREQUEST.fields_by_name['filter'].message_type = _LISTSTORESREQUEST_FILTER -_LISTSTORESRESPONSE.fields_by_name['store'].message_type = feast_dot_core_dot_Store__pb2._STORE -_APPLYFEATURESETREQUEST.fields_by_name['feature_set'].message_type = feast_dot_core_dot_FeatureSet__pb2._FEATURESET -_APPLYFEATURESETRESPONSE.fields_by_name['feature_set'].message_type = feast_dot_core_dot_FeatureSet__pb2._FEATURESET -_APPLYFEATURESETRESPONSE.fields_by_name['status'].enum_type = _APPLYFEATURESETRESPONSE_STATUS -_APPLYFEATURESETRESPONSE_STATUS.containing_type = _APPLYFEATURESETRESPONSE -_UPDATESTOREREQUEST.fields_by_name['store'].message_type = feast_dot_core_dot_Store__pb2._STORE -_UPDATESTORERESPONSE.fields_by_name['store'].message_type = feast_dot_core_dot_Store__pb2._STORE -_UPDATESTORERESPONSE.fields_by_name['status'].enum_type = _UPDATESTORERESPONSE_STATUS -_UPDATESTORERESPONSE_STATUS.containing_type = _UPDATESTORERESPONSE -DESCRIPTOR.message_types_by_name['GetFeatureSetRequest'] = _GETFEATURESETREQUEST -DESCRIPTOR.message_types_by_name['GetFeatureSetResponse'] = _GETFEATURESETRESPONSE -DESCRIPTOR.message_types_by_name['ListFeatureSetsRequest'] = _LISTFEATURESETSREQUEST -DESCRIPTOR.message_types_by_name['ListFeatureSetsResponse'] = _LISTFEATURESETSRESPONSE -DESCRIPTOR.message_types_by_name['ListStoresRequest'] = _LISTSTORESREQUEST -DESCRIPTOR.message_types_by_name['ListStoresResponse'] = _LISTSTORESRESPONSE -DESCRIPTOR.message_types_by_name['ApplyFeatureSetRequest'] = _APPLYFEATURESETREQUEST -DESCRIPTOR.message_types_by_name['ApplyFeatureSetResponse'] = _APPLYFEATURESETRESPONSE -DESCRIPTOR.message_types_by_name['GetFeastCoreVersionRequest'] = _GETFEASTCOREVERSIONREQUEST -DESCRIPTOR.message_types_by_name['GetFeastCoreVersionResponse'] = _GETFEASTCOREVERSIONRESPONSE -DESCRIPTOR.message_types_by_name['UpdateStoreRequest'] = _UPDATESTOREREQUEST -DESCRIPTOR.message_types_by_name['UpdateStoreResponse'] = _UPDATESTORERESPONSE -DESCRIPTOR.message_types_by_name['CreateProjectRequest'] = _CREATEPROJECTREQUEST -DESCRIPTOR.message_types_by_name['CreateProjectResponse'] = _CREATEPROJECTRESPONSE -DESCRIPTOR.message_types_by_name['ArchiveProjectRequest'] = _ARCHIVEPROJECTREQUEST -DESCRIPTOR.message_types_by_name['ArchiveProjectResponse'] = _ARCHIVEPROJECTRESPONSE -DESCRIPTOR.message_types_by_name['ListProjectsRequest'] = _LISTPROJECTSREQUEST -DESCRIPTOR.message_types_by_name['ListProjectsResponse'] = _LISTPROJECTSRESPONSE -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -GetFeatureSetRequest = _reflection.GeneratedProtocolMessageType('GetFeatureSetRequest', (_message.Message,), { - 'DESCRIPTOR' : _GETFEATURESETREQUEST, - '__module__' : 'feast.core.CoreService_pb2' - # @@protoc_insertion_point(class_scope:feast.core.GetFeatureSetRequest) - }) -_sym_db.RegisterMessage(GetFeatureSetRequest) - -GetFeatureSetResponse = _reflection.GeneratedProtocolMessageType('GetFeatureSetResponse', (_message.Message,), { - 'DESCRIPTOR' : _GETFEATURESETRESPONSE, - '__module__' : 'feast.core.CoreService_pb2' - # @@protoc_insertion_point(class_scope:feast.core.GetFeatureSetResponse) - }) -_sym_db.RegisterMessage(GetFeatureSetResponse) - -ListFeatureSetsRequest = _reflection.GeneratedProtocolMessageType('ListFeatureSetsRequest', (_message.Message,), { - - 'Filter' : _reflection.GeneratedProtocolMessageType('Filter', (_message.Message,), { - 'DESCRIPTOR' : _LISTFEATURESETSREQUEST_FILTER, - '__module__' : 'feast.core.CoreService_pb2' - # @@protoc_insertion_point(class_scope:feast.core.ListFeatureSetsRequest.Filter) - }) - , - 'DESCRIPTOR' : _LISTFEATURESETSREQUEST, - '__module__' : 'feast.core.CoreService_pb2' - # @@protoc_insertion_point(class_scope:feast.core.ListFeatureSetsRequest) - }) -_sym_db.RegisterMessage(ListFeatureSetsRequest) -_sym_db.RegisterMessage(ListFeatureSetsRequest.Filter) - -ListFeatureSetsResponse = _reflection.GeneratedProtocolMessageType('ListFeatureSetsResponse', (_message.Message,), { - 'DESCRIPTOR' : _LISTFEATURESETSRESPONSE, - '__module__' : 'feast.core.CoreService_pb2' - # @@protoc_insertion_point(class_scope:feast.core.ListFeatureSetsResponse) - }) -_sym_db.RegisterMessage(ListFeatureSetsResponse) - -ListStoresRequest = _reflection.GeneratedProtocolMessageType('ListStoresRequest', (_message.Message,), { - - 'Filter' : _reflection.GeneratedProtocolMessageType('Filter', (_message.Message,), { - 'DESCRIPTOR' : _LISTSTORESREQUEST_FILTER, - '__module__' : 'feast.core.CoreService_pb2' - # @@protoc_insertion_point(class_scope:feast.core.ListStoresRequest.Filter) - }) - , - 'DESCRIPTOR' : _LISTSTORESREQUEST, - '__module__' : 'feast.core.CoreService_pb2' - # @@protoc_insertion_point(class_scope:feast.core.ListStoresRequest) - }) -_sym_db.RegisterMessage(ListStoresRequest) -_sym_db.RegisterMessage(ListStoresRequest.Filter) - -ListStoresResponse = _reflection.GeneratedProtocolMessageType('ListStoresResponse', (_message.Message,), { - 'DESCRIPTOR' : _LISTSTORESRESPONSE, - '__module__' : 'feast.core.CoreService_pb2' - # @@protoc_insertion_point(class_scope:feast.core.ListStoresResponse) - }) -_sym_db.RegisterMessage(ListStoresResponse) - -ApplyFeatureSetRequest = _reflection.GeneratedProtocolMessageType('ApplyFeatureSetRequest', (_message.Message,), { - 'DESCRIPTOR' : _APPLYFEATURESETREQUEST, - '__module__' : 'feast.core.CoreService_pb2' - # @@protoc_insertion_point(class_scope:feast.core.ApplyFeatureSetRequest) - }) -_sym_db.RegisterMessage(ApplyFeatureSetRequest) - -ApplyFeatureSetResponse = _reflection.GeneratedProtocolMessageType('ApplyFeatureSetResponse', (_message.Message,), { - 'DESCRIPTOR' : _APPLYFEATURESETRESPONSE, - '__module__' : 'feast.core.CoreService_pb2' - # @@protoc_insertion_point(class_scope:feast.core.ApplyFeatureSetResponse) - }) -_sym_db.RegisterMessage(ApplyFeatureSetResponse) - -GetFeastCoreVersionRequest = _reflection.GeneratedProtocolMessageType('GetFeastCoreVersionRequest', (_message.Message,), { - 'DESCRIPTOR' : _GETFEASTCOREVERSIONREQUEST, - '__module__' : 'feast.core.CoreService_pb2' - # @@protoc_insertion_point(class_scope:feast.core.GetFeastCoreVersionRequest) - }) -_sym_db.RegisterMessage(GetFeastCoreVersionRequest) - -GetFeastCoreVersionResponse = _reflection.GeneratedProtocolMessageType('GetFeastCoreVersionResponse', (_message.Message,), { - 'DESCRIPTOR' : _GETFEASTCOREVERSIONRESPONSE, - '__module__' : 'feast.core.CoreService_pb2' - # @@protoc_insertion_point(class_scope:feast.core.GetFeastCoreVersionResponse) - }) -_sym_db.RegisterMessage(GetFeastCoreVersionResponse) - -UpdateStoreRequest = _reflection.GeneratedProtocolMessageType('UpdateStoreRequest', (_message.Message,), { - 'DESCRIPTOR' : _UPDATESTOREREQUEST, - '__module__' : 'feast.core.CoreService_pb2' - # @@protoc_insertion_point(class_scope:feast.core.UpdateStoreRequest) - }) -_sym_db.RegisterMessage(UpdateStoreRequest) - -UpdateStoreResponse = _reflection.GeneratedProtocolMessageType('UpdateStoreResponse', (_message.Message,), { - 'DESCRIPTOR' : _UPDATESTORERESPONSE, - '__module__' : 'feast.core.CoreService_pb2' - # @@protoc_insertion_point(class_scope:feast.core.UpdateStoreResponse) - }) -_sym_db.RegisterMessage(UpdateStoreResponse) - -CreateProjectRequest = _reflection.GeneratedProtocolMessageType('CreateProjectRequest', (_message.Message,), { - 'DESCRIPTOR' : _CREATEPROJECTREQUEST, - '__module__' : 'feast.core.CoreService_pb2' - # @@protoc_insertion_point(class_scope:feast.core.CreateProjectRequest) - }) -_sym_db.RegisterMessage(CreateProjectRequest) - -CreateProjectResponse = _reflection.GeneratedProtocolMessageType('CreateProjectResponse', (_message.Message,), { - 'DESCRIPTOR' : _CREATEPROJECTRESPONSE, - '__module__' : 'feast.core.CoreService_pb2' - # @@protoc_insertion_point(class_scope:feast.core.CreateProjectResponse) - }) -_sym_db.RegisterMessage(CreateProjectResponse) - -ArchiveProjectRequest = _reflection.GeneratedProtocolMessageType('ArchiveProjectRequest', (_message.Message,), { - 'DESCRIPTOR' : _ARCHIVEPROJECTREQUEST, - '__module__' : 'feast.core.CoreService_pb2' - # @@protoc_insertion_point(class_scope:feast.core.ArchiveProjectRequest) - }) -_sym_db.RegisterMessage(ArchiveProjectRequest) - -ArchiveProjectResponse = _reflection.GeneratedProtocolMessageType('ArchiveProjectResponse', (_message.Message,), { - 'DESCRIPTOR' : _ARCHIVEPROJECTRESPONSE, - '__module__' : 'feast.core.CoreService_pb2' - # @@protoc_insertion_point(class_scope:feast.core.ArchiveProjectResponse) - }) -_sym_db.RegisterMessage(ArchiveProjectResponse) - -ListProjectsRequest = _reflection.GeneratedProtocolMessageType('ListProjectsRequest', (_message.Message,), { - 'DESCRIPTOR' : _LISTPROJECTSREQUEST, - '__module__' : 'feast.core.CoreService_pb2' - # @@protoc_insertion_point(class_scope:feast.core.ListProjectsRequest) - }) -_sym_db.RegisterMessage(ListProjectsRequest) - -ListProjectsResponse = _reflection.GeneratedProtocolMessageType('ListProjectsResponse', (_message.Message,), { - 'DESCRIPTOR' : _LISTPROJECTSRESPONSE, - '__module__' : 'feast.core.CoreService_pb2' - # @@protoc_insertion_point(class_scope:feast.core.ListProjectsResponse) - }) -_sym_db.RegisterMessage(ListProjectsResponse) - - -DESCRIPTOR._options = None - -_CORESERVICE = _descriptor.ServiceDescriptor( - name='CoreService', - full_name='feast.core.CoreService', - file=DESCRIPTOR, - index=0, - serialized_options=None, - serialized_start=1368, - serialized_end=2170, - methods=[ - _descriptor.MethodDescriptor( - name='GetFeastCoreVersion', - full_name='feast.core.CoreService.GetFeastCoreVersion', - index=0, - containing_service=None, - input_type=_GETFEASTCOREVERSIONREQUEST, - output_type=_GETFEASTCOREVERSIONRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='GetFeatureSet', - full_name='feast.core.CoreService.GetFeatureSet', - index=1, - containing_service=None, - input_type=_GETFEATURESETREQUEST, - output_type=_GETFEATURESETRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='ListFeatureSets', - full_name='feast.core.CoreService.ListFeatureSets', - index=2, - containing_service=None, - input_type=_LISTFEATURESETSREQUEST, - output_type=_LISTFEATURESETSRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='ListStores', - full_name='feast.core.CoreService.ListStores', - index=3, - containing_service=None, - input_type=_LISTSTORESREQUEST, - output_type=_LISTSTORESRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='ApplyFeatureSet', - full_name='feast.core.CoreService.ApplyFeatureSet', - index=4, - containing_service=None, - input_type=_APPLYFEATURESETREQUEST, - output_type=_APPLYFEATURESETRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='UpdateStore', - full_name='feast.core.CoreService.UpdateStore', - index=5, - containing_service=None, - input_type=_UPDATESTOREREQUEST, - output_type=_UPDATESTORERESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='CreateProject', - full_name='feast.core.CoreService.CreateProject', - index=6, - containing_service=None, - input_type=_CREATEPROJECTREQUEST, - output_type=_CREATEPROJECTRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='ArchiveProject', - full_name='feast.core.CoreService.ArchiveProject', - index=7, - containing_service=None, - input_type=_ARCHIVEPROJECTREQUEST, - output_type=_ARCHIVEPROJECTRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='ListProjects', - full_name='feast.core.CoreService.ListProjects', - index=8, - containing_service=None, - input_type=_LISTPROJECTSREQUEST, - output_type=_LISTPROJECTSRESPONSE, - serialized_options=None, - ), -]) -_sym_db.RegisterServiceDescriptor(_CORESERVICE) - -DESCRIPTOR.services_by_name['CoreService'] = _CORESERVICE - -# @@protoc_insertion_point(module_scope) diff --git a/sdk/python/feast/core/CoreService_pb2.pyi b/sdk/python/feast/core/CoreService_pb2.pyi deleted file mode 100644 index 645226982ad..00000000000 --- a/sdk/python/feast/core/CoreService_pb2.pyi +++ /dev/null @@ -1,429 +0,0 @@ -# @generated by generate_proto_mypy_stubs.py. Do not edit! -import sys -from feast.core.FeatureSet_pb2 import ( - FeatureSet as feast___core___FeatureSet_pb2___FeatureSet, -) - -from feast.core.Store_pb2 import ( - Store as feast___core___Store_pb2___Store, -) - -from google.protobuf.descriptor import ( - Descriptor as google___protobuf___descriptor___Descriptor, - EnumDescriptor as google___protobuf___descriptor___EnumDescriptor, -) - -from google.protobuf.internal.containers import ( - RepeatedCompositeFieldContainer as google___protobuf___internal___containers___RepeatedCompositeFieldContainer, - RepeatedScalarFieldContainer as google___protobuf___internal___containers___RepeatedScalarFieldContainer, -) - -from google.protobuf.message import ( - Message as google___protobuf___message___Message, -) - -from typing import ( - Iterable as typing___Iterable, - List as typing___List, - Optional as typing___Optional, - Text as typing___Text, - Tuple as typing___Tuple, - cast as typing___cast, -) - -from typing_extensions import ( - Literal as typing_extensions___Literal, -) - - -class GetFeatureSetRequest(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - project = ... # type: typing___Text - name = ... # type: typing___Text - version = ... # type: int - - def __init__(self, - *, - project : typing___Optional[typing___Text] = None, - name : typing___Optional[typing___Text] = None, - version : typing___Optional[int] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> GetFeatureSetRequest: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"name",u"project",u"version"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"name",b"name",u"project",b"project",u"version",b"version"]) -> None: ... - -class GetFeatureSetResponse(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - - @property - def feature_set(self) -> feast___core___FeatureSet_pb2___FeatureSet: ... - - def __init__(self, - *, - feature_set : typing___Optional[feast___core___FeatureSet_pb2___FeatureSet] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> GetFeatureSetResponse: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def HasField(self, field_name: typing_extensions___Literal[u"feature_set"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"feature_set"]) -> None: ... - else: - def HasField(self, field_name: typing_extensions___Literal[u"feature_set",b"feature_set"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"feature_set",b"feature_set"]) -> None: ... - -class ListFeatureSetsRequest(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - class Filter(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - project = ... # type: typing___Text - feature_set_name = ... # type: typing___Text - feature_set_version = ... # type: typing___Text - - def __init__(self, - *, - project : typing___Optional[typing___Text] = None, - feature_set_name : typing___Optional[typing___Text] = None, - feature_set_version : typing___Optional[typing___Text] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> ListFeatureSetsRequest.Filter: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"feature_set_name",u"feature_set_version",u"project"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"feature_set_name",b"feature_set_name",u"feature_set_version",b"feature_set_version",u"project",b"project"]) -> None: ... - - - @property - def filter(self) -> ListFeatureSetsRequest.Filter: ... - - def __init__(self, - *, - filter : typing___Optional[ListFeatureSetsRequest.Filter] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> ListFeatureSetsRequest: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def HasField(self, field_name: typing_extensions___Literal[u"filter"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"filter"]) -> None: ... - else: - def HasField(self, field_name: typing_extensions___Literal[u"filter",b"filter"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"filter",b"filter"]) -> None: ... - -class ListFeatureSetsResponse(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - - @property - def feature_sets(self) -> google___protobuf___internal___containers___RepeatedCompositeFieldContainer[feast___core___FeatureSet_pb2___FeatureSet]: ... - - def __init__(self, - *, - feature_sets : typing___Optional[typing___Iterable[feast___core___FeatureSet_pb2___FeatureSet]] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> ListFeatureSetsResponse: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"feature_sets"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"feature_sets",b"feature_sets"]) -> None: ... - -class ListStoresRequest(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - class Filter(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - name = ... # type: typing___Text - - def __init__(self, - *, - name : typing___Optional[typing___Text] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> ListStoresRequest.Filter: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"name"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"name",b"name"]) -> None: ... - - - @property - def filter(self) -> ListStoresRequest.Filter: ... - - def __init__(self, - *, - filter : typing___Optional[ListStoresRequest.Filter] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> ListStoresRequest: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def HasField(self, field_name: typing_extensions___Literal[u"filter"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"filter"]) -> None: ... - else: - def HasField(self, field_name: typing_extensions___Literal[u"filter",b"filter"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"filter",b"filter"]) -> None: ... - -class ListStoresResponse(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - - @property - def store(self) -> google___protobuf___internal___containers___RepeatedCompositeFieldContainer[feast___core___Store_pb2___Store]: ... - - def __init__(self, - *, - store : typing___Optional[typing___Iterable[feast___core___Store_pb2___Store]] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> ListStoresResponse: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"store"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"store",b"store"]) -> None: ... - -class ApplyFeatureSetRequest(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - - @property - def feature_set(self) -> feast___core___FeatureSet_pb2___FeatureSet: ... - - def __init__(self, - *, - feature_set : typing___Optional[feast___core___FeatureSet_pb2___FeatureSet] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> ApplyFeatureSetRequest: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def HasField(self, field_name: typing_extensions___Literal[u"feature_set"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"feature_set"]) -> None: ... - else: - def HasField(self, field_name: typing_extensions___Literal[u"feature_set",b"feature_set"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"feature_set",b"feature_set"]) -> None: ... - -class ApplyFeatureSetResponse(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - class Status(int): - DESCRIPTOR: google___protobuf___descriptor___EnumDescriptor = ... - @classmethod - def Name(cls, number: int) -> str: ... - @classmethod - def Value(cls, name: str) -> ApplyFeatureSetResponse.Status: ... - @classmethod - def keys(cls) -> typing___List[str]: ... - @classmethod - def values(cls) -> typing___List[ApplyFeatureSetResponse.Status]: ... - @classmethod - def items(cls) -> typing___List[typing___Tuple[str, ApplyFeatureSetResponse.Status]]: ... - NO_CHANGE = typing___cast(ApplyFeatureSetResponse.Status, 0) - CREATED = typing___cast(ApplyFeatureSetResponse.Status, 1) - ERROR = typing___cast(ApplyFeatureSetResponse.Status, 2) - NO_CHANGE = typing___cast(ApplyFeatureSetResponse.Status, 0) - CREATED = typing___cast(ApplyFeatureSetResponse.Status, 1) - ERROR = typing___cast(ApplyFeatureSetResponse.Status, 2) - - status = ... # type: ApplyFeatureSetResponse.Status - - @property - def feature_set(self) -> feast___core___FeatureSet_pb2___FeatureSet: ... - - def __init__(self, - *, - feature_set : typing___Optional[feast___core___FeatureSet_pb2___FeatureSet] = None, - status : typing___Optional[ApplyFeatureSetResponse.Status] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> ApplyFeatureSetResponse: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def HasField(self, field_name: typing_extensions___Literal[u"feature_set"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"feature_set",u"status"]) -> None: ... - else: - def HasField(self, field_name: typing_extensions___Literal[u"feature_set",b"feature_set"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"feature_set",b"feature_set",u"status",b"status"]) -> None: ... - -class GetFeastCoreVersionRequest(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - - def __init__(self, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> GetFeastCoreVersionRequest: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - -class GetFeastCoreVersionResponse(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - version = ... # type: typing___Text - - def __init__(self, - *, - version : typing___Optional[typing___Text] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> GetFeastCoreVersionResponse: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"version"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"version",b"version"]) -> None: ... - -class UpdateStoreRequest(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - - @property - def store(self) -> feast___core___Store_pb2___Store: ... - - def __init__(self, - *, - store : typing___Optional[feast___core___Store_pb2___Store] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> UpdateStoreRequest: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def HasField(self, field_name: typing_extensions___Literal[u"store"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"store"]) -> None: ... - else: - def HasField(self, field_name: typing_extensions___Literal[u"store",b"store"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"store",b"store"]) -> None: ... - -class UpdateStoreResponse(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - class Status(int): - DESCRIPTOR: google___protobuf___descriptor___EnumDescriptor = ... - @classmethod - def Name(cls, number: int) -> str: ... - @classmethod - def Value(cls, name: str) -> UpdateStoreResponse.Status: ... - @classmethod - def keys(cls) -> typing___List[str]: ... - @classmethod - def values(cls) -> typing___List[UpdateStoreResponse.Status]: ... - @classmethod - def items(cls) -> typing___List[typing___Tuple[str, UpdateStoreResponse.Status]]: ... - NO_CHANGE = typing___cast(UpdateStoreResponse.Status, 0) - UPDATED = typing___cast(UpdateStoreResponse.Status, 1) - NO_CHANGE = typing___cast(UpdateStoreResponse.Status, 0) - UPDATED = typing___cast(UpdateStoreResponse.Status, 1) - - status = ... # type: UpdateStoreResponse.Status - - @property - def store(self) -> feast___core___Store_pb2___Store: ... - - def __init__(self, - *, - store : typing___Optional[feast___core___Store_pb2___Store] = None, - status : typing___Optional[UpdateStoreResponse.Status] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> UpdateStoreResponse: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def HasField(self, field_name: typing_extensions___Literal[u"store"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"status",u"store"]) -> None: ... - else: - def HasField(self, field_name: typing_extensions___Literal[u"store",b"store"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"status",b"status",u"store",b"store"]) -> None: ... - -class CreateProjectRequest(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - name = ... # type: typing___Text - - def __init__(self, - *, - name : typing___Optional[typing___Text] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> CreateProjectRequest: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"name"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"name",b"name"]) -> None: ... - -class CreateProjectResponse(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - - def __init__(self, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> CreateProjectResponse: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - -class ArchiveProjectRequest(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - name = ... # type: typing___Text - - def __init__(self, - *, - name : typing___Optional[typing___Text] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> ArchiveProjectRequest: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"name"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"name",b"name"]) -> None: ... - -class ArchiveProjectResponse(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - - def __init__(self, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> ArchiveProjectResponse: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - -class ListProjectsRequest(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - - def __init__(self, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> ListProjectsRequest: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - -class ListProjectsResponse(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - projects = ... # type: google___protobuf___internal___containers___RepeatedScalarFieldContainer[typing___Text] - - def __init__(self, - *, - projects : typing___Optional[typing___Iterable[typing___Text]] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> ListProjectsResponse: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"projects"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"projects",b"projects"]) -> None: ... diff --git a/sdk/python/feast/core/CoreService_pb2_grpc.py b/sdk/python/feast/core/CoreService_pb2_grpc.py deleted file mode 100644 index 0e17d0552a2..00000000000 --- a/sdk/python/feast/core/CoreService_pb2_grpc.py +++ /dev/null @@ -1,203 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -import grpc - -from feast.core import CoreService_pb2 as feast_dot_core_dot_CoreService__pb2 - - -class CoreServiceStub(object): - # missing associated documentation comment in .proto file - pass - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.GetFeastCoreVersion = channel.unary_unary( - '/feast.core.CoreService/GetFeastCoreVersion', - request_serializer=feast_dot_core_dot_CoreService__pb2.GetFeastCoreVersionRequest.SerializeToString, - response_deserializer=feast_dot_core_dot_CoreService__pb2.GetFeastCoreVersionResponse.FromString, - ) - self.GetFeatureSet = channel.unary_unary( - '/feast.core.CoreService/GetFeatureSet', - request_serializer=feast_dot_core_dot_CoreService__pb2.GetFeatureSetRequest.SerializeToString, - response_deserializer=feast_dot_core_dot_CoreService__pb2.GetFeatureSetResponse.FromString, - ) - self.ListFeatureSets = channel.unary_unary( - '/feast.core.CoreService/ListFeatureSets', - request_serializer=feast_dot_core_dot_CoreService__pb2.ListFeatureSetsRequest.SerializeToString, - response_deserializer=feast_dot_core_dot_CoreService__pb2.ListFeatureSetsResponse.FromString, - ) - self.ListStores = channel.unary_unary( - '/feast.core.CoreService/ListStores', - request_serializer=feast_dot_core_dot_CoreService__pb2.ListStoresRequest.SerializeToString, - response_deserializer=feast_dot_core_dot_CoreService__pb2.ListStoresResponse.FromString, - ) - self.ApplyFeatureSet = channel.unary_unary( - '/feast.core.CoreService/ApplyFeatureSet', - request_serializer=feast_dot_core_dot_CoreService__pb2.ApplyFeatureSetRequest.SerializeToString, - response_deserializer=feast_dot_core_dot_CoreService__pb2.ApplyFeatureSetResponse.FromString, - ) - self.UpdateStore = channel.unary_unary( - '/feast.core.CoreService/UpdateStore', - request_serializer=feast_dot_core_dot_CoreService__pb2.UpdateStoreRequest.SerializeToString, - response_deserializer=feast_dot_core_dot_CoreService__pb2.UpdateStoreResponse.FromString, - ) - self.CreateProject = channel.unary_unary( - '/feast.core.CoreService/CreateProject', - request_serializer=feast_dot_core_dot_CoreService__pb2.CreateProjectRequest.SerializeToString, - response_deserializer=feast_dot_core_dot_CoreService__pb2.CreateProjectResponse.FromString, - ) - self.ArchiveProject = channel.unary_unary( - '/feast.core.CoreService/ArchiveProject', - request_serializer=feast_dot_core_dot_CoreService__pb2.ArchiveProjectRequest.SerializeToString, - response_deserializer=feast_dot_core_dot_CoreService__pb2.ArchiveProjectResponse.FromString, - ) - self.ListProjects = channel.unary_unary( - '/feast.core.CoreService/ListProjects', - request_serializer=feast_dot_core_dot_CoreService__pb2.ListProjectsRequest.SerializeToString, - response_deserializer=feast_dot_core_dot_CoreService__pb2.ListProjectsResponse.FromString, - ) - - -class CoreServiceServicer(object): - # missing associated documentation comment in .proto file - pass - - def GetFeastCoreVersion(self, request, context): - """Retrieve version information about this Feast deployment - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetFeatureSet(self, request, context): - """Returns a specific feature set - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListFeatureSets(self, request, context): - """Retrieve feature set details given a filter. - - Returns all feature sets matching that filter. If none are found, - an empty list will be returned. - If no filter is provided in the request, the response will contain all the feature - sets currently stored in the registry. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListStores(self, request, context): - """Retrieve store details given a filter. - - Returns all stores matching that filter. If none are found, an empty list will be returned. - If no filter is provided in the request, the response will contain all the stores currently - stored in the registry. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ApplyFeatureSet(self, request, context): - """Create or update and existing feature set. - - This function is idempotent - it will not create a new feature set if schema does not change. - If an existing feature set is updated, core will advance the version number, which will be - returned in response. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def UpdateStore(self, request, context): - """Updates core with the configuration of the store. - - If the changes are valid, core will return the given store configuration in response, and - start or update the necessary feature population jobs for the updated store. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def CreateProject(self, request, context): - """Creates a project. Projects serve as namespaces within which resources like features will be - created. Both feature set names as well as field names must be unique within a project. Project - names themselves must be globally unique. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ArchiveProject(self, request, context): - """Archives a project. Archived projects will continue to exist and function, but won't be visible - through the Core API. Any existing ingestion or serving requests will continue to function, - but will result in warning messages being logged. It is not possible to unarchive a project - through the Core API - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListProjects(self, request, context): - """Lists all projects active projects. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_CoreServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'GetFeastCoreVersion': grpc.unary_unary_rpc_method_handler( - servicer.GetFeastCoreVersion, - request_deserializer=feast_dot_core_dot_CoreService__pb2.GetFeastCoreVersionRequest.FromString, - response_serializer=feast_dot_core_dot_CoreService__pb2.GetFeastCoreVersionResponse.SerializeToString, - ), - 'GetFeatureSet': grpc.unary_unary_rpc_method_handler( - servicer.GetFeatureSet, - request_deserializer=feast_dot_core_dot_CoreService__pb2.GetFeatureSetRequest.FromString, - response_serializer=feast_dot_core_dot_CoreService__pb2.GetFeatureSetResponse.SerializeToString, - ), - 'ListFeatureSets': grpc.unary_unary_rpc_method_handler( - servicer.ListFeatureSets, - request_deserializer=feast_dot_core_dot_CoreService__pb2.ListFeatureSetsRequest.FromString, - response_serializer=feast_dot_core_dot_CoreService__pb2.ListFeatureSetsResponse.SerializeToString, - ), - 'ListStores': grpc.unary_unary_rpc_method_handler( - servicer.ListStores, - request_deserializer=feast_dot_core_dot_CoreService__pb2.ListStoresRequest.FromString, - response_serializer=feast_dot_core_dot_CoreService__pb2.ListStoresResponse.SerializeToString, - ), - 'ApplyFeatureSet': grpc.unary_unary_rpc_method_handler( - servicer.ApplyFeatureSet, - request_deserializer=feast_dot_core_dot_CoreService__pb2.ApplyFeatureSetRequest.FromString, - response_serializer=feast_dot_core_dot_CoreService__pb2.ApplyFeatureSetResponse.SerializeToString, - ), - 'UpdateStore': grpc.unary_unary_rpc_method_handler( - servicer.UpdateStore, - request_deserializer=feast_dot_core_dot_CoreService__pb2.UpdateStoreRequest.FromString, - response_serializer=feast_dot_core_dot_CoreService__pb2.UpdateStoreResponse.SerializeToString, - ), - 'CreateProject': grpc.unary_unary_rpc_method_handler( - servicer.CreateProject, - request_deserializer=feast_dot_core_dot_CoreService__pb2.CreateProjectRequest.FromString, - response_serializer=feast_dot_core_dot_CoreService__pb2.CreateProjectResponse.SerializeToString, - ), - 'ArchiveProject': grpc.unary_unary_rpc_method_handler( - servicer.ArchiveProject, - request_deserializer=feast_dot_core_dot_CoreService__pb2.ArchiveProjectRequest.FromString, - response_serializer=feast_dot_core_dot_CoreService__pb2.ArchiveProjectResponse.SerializeToString, - ), - 'ListProjects': grpc.unary_unary_rpc_method_handler( - servicer.ListProjects, - request_deserializer=feast_dot_core_dot_CoreService__pb2.ListProjectsRequest.FromString, - response_serializer=feast_dot_core_dot_CoreService__pb2.ListProjectsResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'feast.core.CoreService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) diff --git a/sdk/python/feast/core/FeatureSet_pb2.py b/sdk/python/feast/core/FeatureSet_pb2.py deleted file mode 100644 index 991220ccae5..00000000000 --- a/sdk/python/feast/core/FeatureSet_pb2.py +++ /dev/null @@ -1,344 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: feast/core/FeatureSet.proto - -import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) -from google.protobuf.internal import enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from feast.types import Value_pb2 as feast_dot_types_dot_Value__pb2 -from feast.core import Source_pb2 as feast_dot_core_dot_Source__pb2 -from google.protobuf import duration_pb2 as google_dot_protobuf_dot_duration__pb2 -from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 - - -DESCRIPTOR = _descriptor.FileDescriptor( - name='feast/core/FeatureSet.proto', - package='feast.core', - syntax='proto3', - serialized_options=_b('\n\nfeast.coreB\017FeatureSetProtoZ/github.com/gojek/feast/sdk/go/protos/feast/core'), - serialized_pb=_b('\n\x1b\x66\x65\x61st/core/FeatureSet.proto\x12\nfeast.core\x1a\x17\x66\x65\x61st/types/Value.proto\x1a\x17\x66\x65\x61st/core/Source.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"`\n\nFeatureSet\x12(\n\x04spec\x18\x01 \x01(\x0b\x32\x1a.feast.core.FeatureSetSpec\x12(\n\x04meta\x18\x02 \x01(\x0b\x32\x1a.feast.core.FeatureSetMeta\"\xe5\x01\n\x0e\x46\x65\x61tureSetSpec\x12\x0f\n\x07project\x18\x07 \x01(\t\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\x05\x12(\n\x08\x65ntities\x18\x03 \x03(\x0b\x32\x16.feast.core.EntitySpec\x12)\n\x08\x66\x65\x61tures\x18\x04 \x03(\x0b\x32\x17.feast.core.FeatureSpec\x12*\n\x07max_age\x18\x05 \x01(\x0b\x32\x19.google.protobuf.Duration\x12\"\n\x06source\x18\x06 \x01(\x0b\x32\x12.feast.core.Source\"K\n\nEntitySpec\x12\x0c\n\x04name\x18\x01 \x01(\t\x12/\n\nvalue_type\x18\x02 \x01(\x0e\x32\x1b.feast.types.ValueType.Enum\"L\n\x0b\x46\x65\x61tureSpec\x12\x0c\n\x04name\x18\x01 \x01(\t\x12/\n\nvalue_type\x18\x02 \x01(\x0e\x32\x1b.feast.types.ValueType.Enum\"u\n\x0e\x46\x65\x61tureSetMeta\x12\x35\n\x11\x63reated_timestamp\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12,\n\x06status\x18\x02 \x01(\x0e\x32\x1c.feast.core.FeatureSetStatus*L\n\x10\x46\x65\x61tureSetStatus\x12\x12\n\x0eSTATUS_INVALID\x10\x00\x12\x12\n\x0eSTATUS_PENDING\x10\x01\x12\x10\n\x0cSTATUS_READY\x10\x02\x42N\n\nfeast.coreB\x0f\x46\x65\x61tureSetProtoZ/github.com/gojek/feast/sdk/go/protos/feast/coreb\x06proto3') - , - dependencies=[feast_dot_types_dot_Value__pb2.DESCRIPTOR,feast_dot_core_dot_Source__pb2.DESCRIPTOR,google_dot_protobuf_dot_duration__pb2.DESCRIPTOR,google_dot_protobuf_dot_timestamp__pb2.DESCRIPTOR,]) - -_FEATURESETSTATUS = _descriptor.EnumDescriptor( - name='FeatureSetStatus', - full_name='feast.core.FeatureSetStatus', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='STATUS_INVALID', index=0, number=0, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='STATUS_PENDING', index=1, number=1, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='STATUS_READY', index=2, number=2, - serialized_options=None, - type=None), - ], - containing_type=None, - serialized_options=None, - serialized_start=762, - serialized_end=838, -) -_sym_db.RegisterEnumDescriptor(_FEATURESETSTATUS) - -FeatureSetStatus = enum_type_wrapper.EnumTypeWrapper(_FEATURESETSTATUS) -STATUS_INVALID = 0 -STATUS_PENDING = 1 -STATUS_READY = 2 - - - -_FEATURESET = _descriptor.Descriptor( - name='FeatureSet', - full_name='feast.core.FeatureSet', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='spec', full_name='feast.core.FeatureSet.spec', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='meta', full_name='feast.core.FeatureSet.meta', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=158, - serialized_end=254, -) - - -_FEATURESETSPEC = _descriptor.Descriptor( - name='FeatureSetSpec', - full_name='feast.core.FeatureSetSpec', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='project', full_name='feast.core.FeatureSetSpec.project', index=0, - number=7, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='name', full_name='feast.core.FeatureSetSpec.name', index=1, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='version', full_name='feast.core.FeatureSetSpec.version', index=2, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='entities', full_name='feast.core.FeatureSetSpec.entities', index=3, - number=3, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='features', full_name='feast.core.FeatureSetSpec.features', index=4, - number=4, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='max_age', full_name='feast.core.FeatureSetSpec.max_age', index=5, - number=5, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='source', full_name='feast.core.FeatureSetSpec.source', index=6, - number=6, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=257, - serialized_end=486, -) - - -_ENTITYSPEC = _descriptor.Descriptor( - name='EntitySpec', - full_name='feast.core.EntitySpec', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='name', full_name='feast.core.EntitySpec.name', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='value_type', full_name='feast.core.EntitySpec.value_type', index=1, - number=2, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=488, - serialized_end=563, -) - - -_FEATURESPEC = _descriptor.Descriptor( - name='FeatureSpec', - full_name='feast.core.FeatureSpec', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='name', full_name='feast.core.FeatureSpec.name', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='value_type', full_name='feast.core.FeatureSpec.value_type', index=1, - number=2, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=565, - serialized_end=641, -) - - -_FEATURESETMETA = _descriptor.Descriptor( - name='FeatureSetMeta', - full_name='feast.core.FeatureSetMeta', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='created_timestamp', full_name='feast.core.FeatureSetMeta.created_timestamp', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='status', full_name='feast.core.FeatureSetMeta.status', index=1, - number=2, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=643, - serialized_end=760, -) - -_FEATURESET.fields_by_name['spec'].message_type = _FEATURESETSPEC -_FEATURESET.fields_by_name['meta'].message_type = _FEATURESETMETA -_FEATURESETSPEC.fields_by_name['entities'].message_type = _ENTITYSPEC -_FEATURESETSPEC.fields_by_name['features'].message_type = _FEATURESPEC -_FEATURESETSPEC.fields_by_name['max_age'].message_type = google_dot_protobuf_dot_duration__pb2._DURATION -_FEATURESETSPEC.fields_by_name['source'].message_type = feast_dot_core_dot_Source__pb2._SOURCE -_ENTITYSPEC.fields_by_name['value_type'].enum_type = feast_dot_types_dot_Value__pb2._VALUETYPE_ENUM -_FEATURESPEC.fields_by_name['value_type'].enum_type = feast_dot_types_dot_Value__pb2._VALUETYPE_ENUM -_FEATURESETMETA.fields_by_name['created_timestamp'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP -_FEATURESETMETA.fields_by_name['status'].enum_type = _FEATURESETSTATUS -DESCRIPTOR.message_types_by_name['FeatureSet'] = _FEATURESET -DESCRIPTOR.message_types_by_name['FeatureSetSpec'] = _FEATURESETSPEC -DESCRIPTOR.message_types_by_name['EntitySpec'] = _ENTITYSPEC -DESCRIPTOR.message_types_by_name['FeatureSpec'] = _FEATURESPEC -DESCRIPTOR.message_types_by_name['FeatureSetMeta'] = _FEATURESETMETA -DESCRIPTOR.enum_types_by_name['FeatureSetStatus'] = _FEATURESETSTATUS -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -FeatureSet = _reflection.GeneratedProtocolMessageType('FeatureSet', (_message.Message,), { - 'DESCRIPTOR' : _FEATURESET, - '__module__' : 'feast.core.FeatureSet_pb2' - # @@protoc_insertion_point(class_scope:feast.core.FeatureSet) - }) -_sym_db.RegisterMessage(FeatureSet) - -FeatureSetSpec = _reflection.GeneratedProtocolMessageType('FeatureSetSpec', (_message.Message,), { - 'DESCRIPTOR' : _FEATURESETSPEC, - '__module__' : 'feast.core.FeatureSet_pb2' - # @@protoc_insertion_point(class_scope:feast.core.FeatureSetSpec) - }) -_sym_db.RegisterMessage(FeatureSetSpec) - -EntitySpec = _reflection.GeneratedProtocolMessageType('EntitySpec', (_message.Message,), { - 'DESCRIPTOR' : _ENTITYSPEC, - '__module__' : 'feast.core.FeatureSet_pb2' - # @@protoc_insertion_point(class_scope:feast.core.EntitySpec) - }) -_sym_db.RegisterMessage(EntitySpec) - -FeatureSpec = _reflection.GeneratedProtocolMessageType('FeatureSpec', (_message.Message,), { - 'DESCRIPTOR' : _FEATURESPEC, - '__module__' : 'feast.core.FeatureSet_pb2' - # @@protoc_insertion_point(class_scope:feast.core.FeatureSpec) - }) -_sym_db.RegisterMessage(FeatureSpec) - -FeatureSetMeta = _reflection.GeneratedProtocolMessageType('FeatureSetMeta', (_message.Message,), { - 'DESCRIPTOR' : _FEATURESETMETA, - '__module__' : 'feast.core.FeatureSet_pb2' - # @@protoc_insertion_point(class_scope:feast.core.FeatureSetMeta) - }) -_sym_db.RegisterMessage(FeatureSetMeta) - - -DESCRIPTOR._options = None -# @@protoc_insertion_point(module_scope) diff --git a/sdk/python/feast/core/FeatureSet_pb2.pyi b/sdk/python/feast/core/FeatureSet_pb2.pyi deleted file mode 100644 index c663c70c682..00000000000 --- a/sdk/python/feast/core/FeatureSet_pb2.pyi +++ /dev/null @@ -1,188 +0,0 @@ -# @generated by generate_proto_mypy_stubs.py. Do not edit! -import sys -from feast.core.Source_pb2 import ( - Source as feast___core___Source_pb2___Source, -) - -from feast.types.Value_pb2 import ( - ValueType as feast___types___Value_pb2___ValueType, -) - -from google.protobuf.descriptor import ( - Descriptor as google___protobuf___descriptor___Descriptor, - EnumDescriptor as google___protobuf___descriptor___EnumDescriptor, -) - -from google.protobuf.duration_pb2 import ( - Duration as google___protobuf___duration_pb2___Duration, -) - -from google.protobuf.internal.containers import ( - RepeatedCompositeFieldContainer as google___protobuf___internal___containers___RepeatedCompositeFieldContainer, -) - -from google.protobuf.message import ( - Message as google___protobuf___message___Message, -) - -from google.protobuf.timestamp_pb2 import ( - Timestamp as google___protobuf___timestamp_pb2___Timestamp, -) - -from typing import ( - Iterable as typing___Iterable, - List as typing___List, - Optional as typing___Optional, - Text as typing___Text, - Tuple as typing___Tuple, - cast as typing___cast, -) - -from typing_extensions import ( - Literal as typing_extensions___Literal, -) - - -class FeatureSetStatus(int): - DESCRIPTOR: google___protobuf___descriptor___EnumDescriptor = ... - @classmethod - def Name(cls, number: int) -> str: ... - @classmethod - def Value(cls, name: str) -> FeatureSetStatus: ... - @classmethod - def keys(cls) -> typing___List[str]: ... - @classmethod - def values(cls) -> typing___List[FeatureSetStatus]: ... - @classmethod - def items(cls) -> typing___List[typing___Tuple[str, FeatureSetStatus]]: ... - STATUS_INVALID = typing___cast(FeatureSetStatus, 0) - STATUS_PENDING = typing___cast(FeatureSetStatus, 1) - STATUS_READY = typing___cast(FeatureSetStatus, 2) -STATUS_INVALID = typing___cast(FeatureSetStatus, 0) -STATUS_PENDING = typing___cast(FeatureSetStatus, 1) -STATUS_READY = typing___cast(FeatureSetStatus, 2) - -class FeatureSet(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - - @property - def spec(self) -> FeatureSetSpec: ... - - @property - def meta(self) -> FeatureSetMeta: ... - - def __init__(self, - *, - spec : typing___Optional[FeatureSetSpec] = None, - meta : typing___Optional[FeatureSetMeta] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> FeatureSet: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def HasField(self, field_name: typing_extensions___Literal[u"meta",u"spec"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"meta",u"spec"]) -> None: ... - else: - def HasField(self, field_name: typing_extensions___Literal[u"meta",b"meta",u"spec",b"spec"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"meta",b"meta",u"spec",b"spec"]) -> None: ... - -class FeatureSetSpec(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - project = ... # type: typing___Text - name = ... # type: typing___Text - version = ... # type: int - - @property - def entities(self) -> google___protobuf___internal___containers___RepeatedCompositeFieldContainer[EntitySpec]: ... - - @property - def features(self) -> google___protobuf___internal___containers___RepeatedCompositeFieldContainer[FeatureSpec]: ... - - @property - def max_age(self) -> google___protobuf___duration_pb2___Duration: ... - - @property - def source(self) -> feast___core___Source_pb2___Source: ... - - def __init__(self, - *, - project : typing___Optional[typing___Text] = None, - name : typing___Optional[typing___Text] = None, - version : typing___Optional[int] = None, - entities : typing___Optional[typing___Iterable[EntitySpec]] = None, - features : typing___Optional[typing___Iterable[FeatureSpec]] = None, - max_age : typing___Optional[google___protobuf___duration_pb2___Duration] = None, - source : typing___Optional[feast___core___Source_pb2___Source] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> FeatureSetSpec: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def HasField(self, field_name: typing_extensions___Literal[u"max_age",u"source"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"entities",u"features",u"max_age",u"name",u"project",u"source",u"version"]) -> None: ... - else: - def HasField(self, field_name: typing_extensions___Literal[u"max_age",b"max_age",u"source",b"source"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"entities",b"entities",u"features",b"features",u"max_age",b"max_age",u"name",b"name",u"project",b"project",u"source",b"source",u"version",b"version"]) -> None: ... - -class EntitySpec(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - name = ... # type: typing___Text - value_type = ... # type: feast___types___Value_pb2___ValueType.Enum - - def __init__(self, - *, - name : typing___Optional[typing___Text] = None, - value_type : typing___Optional[feast___types___Value_pb2___ValueType.Enum] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> EntitySpec: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"name",u"value_type"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"name",b"name",u"value_type",b"value_type"]) -> None: ... - -class FeatureSpec(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - name = ... # type: typing___Text - value_type = ... # type: feast___types___Value_pb2___ValueType.Enum - - def __init__(self, - *, - name : typing___Optional[typing___Text] = None, - value_type : typing___Optional[feast___types___Value_pb2___ValueType.Enum] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> FeatureSpec: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"name",u"value_type"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"name",b"name",u"value_type",b"value_type"]) -> None: ... - -class FeatureSetMeta(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - status = ... # type: FeatureSetStatus - - @property - def created_timestamp(self) -> google___protobuf___timestamp_pb2___Timestamp: ... - - def __init__(self, - *, - created_timestamp : typing___Optional[google___protobuf___timestamp_pb2___Timestamp] = None, - status : typing___Optional[FeatureSetStatus] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> FeatureSetMeta: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def HasField(self, field_name: typing_extensions___Literal[u"created_timestamp"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"created_timestamp",u"status"]) -> None: ... - else: - def HasField(self, field_name: typing_extensions___Literal[u"created_timestamp",b"created_timestamp"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"created_timestamp",b"created_timestamp",u"status",b"status"]) -> None: ... diff --git a/sdk/python/feast/core/FeatureSet_pb2_grpc.py b/sdk/python/feast/core/FeatureSet_pb2_grpc.py deleted file mode 100644 index a89435267cb..00000000000 --- a/sdk/python/feast/core/FeatureSet_pb2_grpc.py +++ /dev/null @@ -1,3 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -import grpc - diff --git a/sdk/python/feast/core/Source_pb2.py b/sdk/python/feast/core/Source_pb2.py deleted file mode 100644 index e0d0dd64313..00000000000 --- a/sdk/python/feast/core/Source_pb2.py +++ /dev/null @@ -1,159 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: feast/core/Source.proto - -import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) -from google.protobuf.internal import enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor.FileDescriptor( - name='feast/core/Source.proto', - package='feast.core', - syntax='proto3', - serialized_options=_b('\n\nfeast.coreB\013SourceProtoZ/github.com/gojek/feast/sdk/go/protos/feast/core'), - serialized_pb=_b('\n\x17\x66\x65\x61st/core/Source.proto\x12\nfeast.core\"}\n\x06Source\x12$\n\x04type\x18\x01 \x01(\x0e\x32\x16.feast.core.SourceType\x12<\n\x13kafka_source_config\x18\x02 \x01(\x0b\x32\x1d.feast.core.KafkaSourceConfigH\x00\x42\x0f\n\rsource_config\"=\n\x11KafkaSourceConfig\x12\x19\n\x11\x62ootstrap_servers\x18\x01 \x01(\t\x12\r\n\x05topic\x18\x02 \x01(\t*$\n\nSourceType\x12\x0b\n\x07INVALID\x10\x00\x12\t\n\x05KAFKA\x10\x01\x42J\n\nfeast.coreB\x0bSourceProtoZ/github.com/gojek/feast/sdk/go/protos/feast/coreb\x06proto3') -) - -_SOURCETYPE = _descriptor.EnumDescriptor( - name='SourceType', - full_name='feast.core.SourceType', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='INVALID', index=0, number=0, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='KAFKA', index=1, number=1, - serialized_options=None, - type=None), - ], - containing_type=None, - serialized_options=None, - serialized_start=229, - serialized_end=265, -) -_sym_db.RegisterEnumDescriptor(_SOURCETYPE) - -SourceType = enum_type_wrapper.EnumTypeWrapper(_SOURCETYPE) -INVALID = 0 -KAFKA = 1 - - - -_SOURCE = _descriptor.Descriptor( - name='Source', - full_name='feast.core.Source', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='type', full_name='feast.core.Source.type', index=0, - number=1, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='kafka_source_config', full_name='feast.core.Source.kafka_source_config', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - _descriptor.OneofDescriptor( - name='source_config', full_name='feast.core.Source.source_config', - index=0, containing_type=None, fields=[]), - ], - serialized_start=39, - serialized_end=164, -) - - -_KAFKASOURCECONFIG = _descriptor.Descriptor( - name='KafkaSourceConfig', - full_name='feast.core.KafkaSourceConfig', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='bootstrap_servers', full_name='feast.core.KafkaSourceConfig.bootstrap_servers', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='topic', full_name='feast.core.KafkaSourceConfig.topic', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=166, - serialized_end=227, -) - -_SOURCE.fields_by_name['type'].enum_type = _SOURCETYPE -_SOURCE.fields_by_name['kafka_source_config'].message_type = _KAFKASOURCECONFIG -_SOURCE.oneofs_by_name['source_config'].fields.append( - _SOURCE.fields_by_name['kafka_source_config']) -_SOURCE.fields_by_name['kafka_source_config'].containing_oneof = _SOURCE.oneofs_by_name['source_config'] -DESCRIPTOR.message_types_by_name['Source'] = _SOURCE -DESCRIPTOR.message_types_by_name['KafkaSourceConfig'] = _KAFKASOURCECONFIG -DESCRIPTOR.enum_types_by_name['SourceType'] = _SOURCETYPE -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -Source = _reflection.GeneratedProtocolMessageType('Source', (_message.Message,), { - 'DESCRIPTOR' : _SOURCE, - '__module__' : 'feast.core.Source_pb2' - # @@protoc_insertion_point(class_scope:feast.core.Source) - }) -_sym_db.RegisterMessage(Source) - -KafkaSourceConfig = _reflection.GeneratedProtocolMessageType('KafkaSourceConfig', (_message.Message,), { - 'DESCRIPTOR' : _KAFKASOURCECONFIG, - '__module__' : 'feast.core.Source_pb2' - # @@protoc_insertion_point(class_scope:feast.core.KafkaSourceConfig) - }) -_sym_db.RegisterMessage(KafkaSourceConfig) - - -DESCRIPTOR._options = None -# @@protoc_insertion_point(module_scope) diff --git a/sdk/python/feast/core/Source_pb2.pyi b/sdk/python/feast/core/Source_pb2.pyi deleted file mode 100644 index 0521ac34f80..00000000000 --- a/sdk/python/feast/core/Source_pb2.pyi +++ /dev/null @@ -1,83 +0,0 @@ -# @generated by generate_proto_mypy_stubs.py. Do not edit! -import sys -from google.protobuf.descriptor import ( - Descriptor as google___protobuf___descriptor___Descriptor, - EnumDescriptor as google___protobuf___descriptor___EnumDescriptor, -) - -from google.protobuf.message import ( - Message as google___protobuf___message___Message, -) - -from typing import ( - List as typing___List, - Optional as typing___Optional, - Text as typing___Text, - Tuple as typing___Tuple, - cast as typing___cast, -) - -from typing_extensions import ( - Literal as typing_extensions___Literal, -) - - -class SourceType(int): - DESCRIPTOR: google___protobuf___descriptor___EnumDescriptor = ... - @classmethod - def Name(cls, number: int) -> str: ... - @classmethod - def Value(cls, name: str) -> SourceType: ... - @classmethod - def keys(cls) -> typing___List[str]: ... - @classmethod - def values(cls) -> typing___List[SourceType]: ... - @classmethod - def items(cls) -> typing___List[typing___Tuple[str, SourceType]]: ... - INVALID = typing___cast(SourceType, 0) - KAFKA = typing___cast(SourceType, 1) -INVALID = typing___cast(SourceType, 0) -KAFKA = typing___cast(SourceType, 1) - -class Source(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - type = ... # type: SourceType - - @property - def kafka_source_config(self) -> KafkaSourceConfig: ... - - def __init__(self, - *, - type : typing___Optional[SourceType] = None, - kafka_source_config : typing___Optional[KafkaSourceConfig] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> Source: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def HasField(self, field_name: typing_extensions___Literal[u"kafka_source_config",u"source_config"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"kafka_source_config",u"source_config",u"type"]) -> None: ... - else: - def HasField(self, field_name: typing_extensions___Literal[u"kafka_source_config",b"kafka_source_config",u"source_config",b"source_config"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"kafka_source_config",b"kafka_source_config",u"source_config",b"source_config",u"type",b"type"]) -> None: ... - def WhichOneof(self, oneof_group: typing_extensions___Literal[u"source_config",b"source_config"]) -> typing_extensions___Literal["kafka_source_config"]: ... - -class KafkaSourceConfig(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - bootstrap_servers = ... # type: typing___Text - topic = ... # type: typing___Text - - def __init__(self, - *, - bootstrap_servers : typing___Optional[typing___Text] = None, - topic : typing___Optional[typing___Text] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> KafkaSourceConfig: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"bootstrap_servers",u"topic"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"bootstrap_servers",b"bootstrap_servers",u"topic",b"topic"]) -> None: ... diff --git a/sdk/python/feast/core/Source_pb2_grpc.py b/sdk/python/feast/core/Source_pb2_grpc.py deleted file mode 100644 index a89435267cb..00000000000 --- a/sdk/python/feast/core/Source_pb2_grpc.py +++ /dev/null @@ -1,3 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -import grpc - diff --git a/sdk/python/feast/core/Store_pb2.py b/sdk/python/feast/core/Store_pb2.py deleted file mode 100644 index 716a597b9a3..00000000000 --- a/sdk/python/feast/core/Store_pb2.py +++ /dev/null @@ -1,346 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: feast/core/Store.proto - -import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor.FileDescriptor( - name='feast/core/Store.proto', - package='feast.core', - syntax='proto3', - serialized_options=_b('\n\nfeast.coreB\nStoreProtoZ/github.com/gojek/feast/sdk/go/protos/feast/core'), - serialized_pb=_b('\n\x16\x66\x65\x61st/core/Store.proto\x12\nfeast.core\"\xca\x04\n\x05Store\x12\x0c\n\x04name\x18\x01 \x01(\t\x12)\n\x04type\x18\x02 \x01(\x0e\x32\x1b.feast.core.Store.StoreType\x12\x35\n\rsubscriptions\x18\x04 \x03(\x0b\x32\x1e.feast.core.Store.Subscription\x12\x35\n\x0credis_config\x18\x0b \x01(\x0b\x32\x1d.feast.core.Store.RedisConfigH\x00\x12;\n\x0f\x62igquery_config\x18\x0c \x01(\x0b\x32 .feast.core.Store.BigQueryConfigH\x00\x12=\n\x10\x63\x61ssandra_config\x18\r \x01(\x0b\x32!.feast.core.Store.CassandraConfigH\x00\x1a)\n\x0bRedisConfig\x12\x0c\n\x04host\x18\x01 \x01(\t\x12\x0c\n\x04port\x18\x02 \x01(\x05\x1a\x38\n\x0e\x42igQueryConfig\x12\x12\n\nproject_id\x18\x01 \x01(\t\x12\x12\n\ndataset_id\x18\x02 \x01(\t\x1a-\n\x0f\x43\x61ssandraConfig\x12\x0c\n\x04host\x18\x01 \x01(\t\x12\x0c\n\x04port\x18\x02 \x01(\x05\x1a>\n\x0cSubscription\x12\x0f\n\x07project\x18\x03 \x01(\t\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\t\"@\n\tStoreType\x12\x0b\n\x07INVALID\x10\x00\x12\t\n\x05REDIS\x10\x01\x12\x0c\n\x08\x42IGQUERY\x10\x02\x12\r\n\tCASSANDRA\x10\x03\x42\x08\n\x06\x63onfigBI\n\nfeast.coreB\nStoreProtoZ/github.com/gojek/feast/sdk/go/protos/feast/coreb\x06proto3') -) - - - -_STORE_STORETYPE = _descriptor.EnumDescriptor( - name='StoreType', - full_name='feast.core.Store.StoreType', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='INVALID', index=0, number=0, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='REDIS', index=1, number=1, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='BIGQUERY', index=2, number=2, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='CASSANDRA', index=3, number=3, - serialized_options=None, - type=None), - ], - containing_type=None, - serialized_options=None, - serialized_start=551, - serialized_end=615, -) -_sym_db.RegisterEnumDescriptor(_STORE_STORETYPE) - - -_STORE_REDISCONFIG = _descriptor.Descriptor( - name='RedisConfig', - full_name='feast.core.Store.RedisConfig', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='host', full_name='feast.core.Store.RedisConfig.host', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='port', full_name='feast.core.Store.RedisConfig.port', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=339, - serialized_end=380, -) - -_STORE_BIGQUERYCONFIG = _descriptor.Descriptor( - name='BigQueryConfig', - full_name='feast.core.Store.BigQueryConfig', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='project_id', full_name='feast.core.Store.BigQueryConfig.project_id', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='dataset_id', full_name='feast.core.Store.BigQueryConfig.dataset_id', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=382, - serialized_end=438, -) - -_STORE_CASSANDRACONFIG = _descriptor.Descriptor( - name='CassandraConfig', - full_name='feast.core.Store.CassandraConfig', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='host', full_name='feast.core.Store.CassandraConfig.host', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='port', full_name='feast.core.Store.CassandraConfig.port', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=440, - serialized_end=485, -) - -_STORE_SUBSCRIPTION = _descriptor.Descriptor( - name='Subscription', - full_name='feast.core.Store.Subscription', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='project', full_name='feast.core.Store.Subscription.project', index=0, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='name', full_name='feast.core.Store.Subscription.name', index=1, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='version', full_name='feast.core.Store.Subscription.version', index=2, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=487, - serialized_end=549, -) - -_STORE = _descriptor.Descriptor( - name='Store', - full_name='feast.core.Store', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='name', full_name='feast.core.Store.name', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='type', full_name='feast.core.Store.type', index=1, - number=2, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='subscriptions', full_name='feast.core.Store.subscriptions', index=2, - number=4, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='redis_config', full_name='feast.core.Store.redis_config', index=3, - number=11, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='bigquery_config', full_name='feast.core.Store.bigquery_config', index=4, - number=12, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='cassandra_config', full_name='feast.core.Store.cassandra_config', index=5, - number=13, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[_STORE_REDISCONFIG, _STORE_BIGQUERYCONFIG, _STORE_CASSANDRACONFIG, _STORE_SUBSCRIPTION, ], - enum_types=[ - _STORE_STORETYPE, - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - _descriptor.OneofDescriptor( - name='config', full_name='feast.core.Store.config', - index=0, containing_type=None, fields=[]), - ], - serialized_start=39, - serialized_end=625, -) - -_STORE_REDISCONFIG.containing_type = _STORE -_STORE_BIGQUERYCONFIG.containing_type = _STORE -_STORE_CASSANDRACONFIG.containing_type = _STORE -_STORE_SUBSCRIPTION.containing_type = _STORE -_STORE.fields_by_name['type'].enum_type = _STORE_STORETYPE -_STORE.fields_by_name['subscriptions'].message_type = _STORE_SUBSCRIPTION -_STORE.fields_by_name['redis_config'].message_type = _STORE_REDISCONFIG -_STORE.fields_by_name['bigquery_config'].message_type = _STORE_BIGQUERYCONFIG -_STORE.fields_by_name['cassandra_config'].message_type = _STORE_CASSANDRACONFIG -_STORE_STORETYPE.containing_type = _STORE -_STORE.oneofs_by_name['config'].fields.append( - _STORE.fields_by_name['redis_config']) -_STORE.fields_by_name['redis_config'].containing_oneof = _STORE.oneofs_by_name['config'] -_STORE.oneofs_by_name['config'].fields.append( - _STORE.fields_by_name['bigquery_config']) -_STORE.fields_by_name['bigquery_config'].containing_oneof = _STORE.oneofs_by_name['config'] -_STORE.oneofs_by_name['config'].fields.append( - _STORE.fields_by_name['cassandra_config']) -_STORE.fields_by_name['cassandra_config'].containing_oneof = _STORE.oneofs_by_name['config'] -DESCRIPTOR.message_types_by_name['Store'] = _STORE -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -Store = _reflection.GeneratedProtocolMessageType('Store', (_message.Message,), { - - 'RedisConfig' : _reflection.GeneratedProtocolMessageType('RedisConfig', (_message.Message,), { - 'DESCRIPTOR' : _STORE_REDISCONFIG, - '__module__' : 'feast.core.Store_pb2' - # @@protoc_insertion_point(class_scope:feast.core.Store.RedisConfig) - }) - , - - 'BigQueryConfig' : _reflection.GeneratedProtocolMessageType('BigQueryConfig', (_message.Message,), { - 'DESCRIPTOR' : _STORE_BIGQUERYCONFIG, - '__module__' : 'feast.core.Store_pb2' - # @@protoc_insertion_point(class_scope:feast.core.Store.BigQueryConfig) - }) - , - - 'CassandraConfig' : _reflection.GeneratedProtocolMessageType('CassandraConfig', (_message.Message,), { - 'DESCRIPTOR' : _STORE_CASSANDRACONFIG, - '__module__' : 'feast.core.Store_pb2' - # @@protoc_insertion_point(class_scope:feast.core.Store.CassandraConfig) - }) - , - - 'Subscription' : _reflection.GeneratedProtocolMessageType('Subscription', (_message.Message,), { - 'DESCRIPTOR' : _STORE_SUBSCRIPTION, - '__module__' : 'feast.core.Store_pb2' - # @@protoc_insertion_point(class_scope:feast.core.Store.Subscription) - }) - , - 'DESCRIPTOR' : _STORE, - '__module__' : 'feast.core.Store_pb2' - # @@protoc_insertion_point(class_scope:feast.core.Store) - }) -_sym_db.RegisterMessage(Store) -_sym_db.RegisterMessage(Store.RedisConfig) -_sym_db.RegisterMessage(Store.BigQueryConfig) -_sym_db.RegisterMessage(Store.CassandraConfig) -_sym_db.RegisterMessage(Store.Subscription) - - -DESCRIPTOR._options = None -# @@protoc_insertion_point(module_scope) diff --git a/sdk/python/feast/core/Store_pb2.pyi b/sdk/python/feast/core/Store_pb2.pyi deleted file mode 100644 index 541bcd329bb..00000000000 --- a/sdk/python/feast/core/Store_pb2.pyi +++ /dev/null @@ -1,165 +0,0 @@ -# @generated by generate_proto_mypy_stubs.py. Do not edit! -import sys -from google.protobuf.descriptor import ( - Descriptor as google___protobuf___descriptor___Descriptor, - EnumDescriptor as google___protobuf___descriptor___EnumDescriptor, -) - -from google.protobuf.internal.containers import ( - RepeatedCompositeFieldContainer as google___protobuf___internal___containers___RepeatedCompositeFieldContainer, -) - -from google.protobuf.message import ( - Message as google___protobuf___message___Message, -) - -from typing import ( - Iterable as typing___Iterable, - List as typing___List, - Optional as typing___Optional, - Text as typing___Text, - Tuple as typing___Tuple, - cast as typing___cast, -) - -from typing_extensions import ( - Literal as typing_extensions___Literal, -) - - -class Store(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - class StoreType(int): - DESCRIPTOR: google___protobuf___descriptor___EnumDescriptor = ... - @classmethod - def Name(cls, number: int) -> str: ... - @classmethod - def Value(cls, name: str) -> Store.StoreType: ... - @classmethod - def keys(cls) -> typing___List[str]: ... - @classmethod - def values(cls) -> typing___List[Store.StoreType]: ... - @classmethod - def items(cls) -> typing___List[typing___Tuple[str, Store.StoreType]]: ... - INVALID = typing___cast(Store.StoreType, 0) - REDIS = typing___cast(Store.StoreType, 1) - BIGQUERY = typing___cast(Store.StoreType, 2) - CASSANDRA = typing___cast(Store.StoreType, 3) - INVALID = typing___cast(Store.StoreType, 0) - REDIS = typing___cast(Store.StoreType, 1) - BIGQUERY = typing___cast(Store.StoreType, 2) - CASSANDRA = typing___cast(Store.StoreType, 3) - - class RedisConfig(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - host = ... # type: typing___Text - port = ... # type: int - - def __init__(self, - *, - host : typing___Optional[typing___Text] = None, - port : typing___Optional[int] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> Store.RedisConfig: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"host",u"port"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"host",b"host",u"port",b"port"]) -> None: ... - - class BigQueryConfig(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - project_id = ... # type: typing___Text - dataset_id = ... # type: typing___Text - - def __init__(self, - *, - project_id : typing___Optional[typing___Text] = None, - dataset_id : typing___Optional[typing___Text] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> Store.BigQueryConfig: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"dataset_id",u"project_id"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"dataset_id",b"dataset_id",u"project_id",b"project_id"]) -> None: ... - - class CassandraConfig(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - host = ... # type: typing___Text - port = ... # type: int - - def __init__(self, - *, - host : typing___Optional[typing___Text] = None, - port : typing___Optional[int] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> Store.CassandraConfig: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"host",u"port"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"host",b"host",u"port",b"port"]) -> None: ... - - class Subscription(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - project = ... # type: typing___Text - name = ... # type: typing___Text - version = ... # type: typing___Text - - def __init__(self, - *, - project : typing___Optional[typing___Text] = None, - name : typing___Optional[typing___Text] = None, - version : typing___Optional[typing___Text] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> Store.Subscription: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"name",u"project",u"version"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"name",b"name",u"project",b"project",u"version",b"version"]) -> None: ... - - name = ... # type: typing___Text - type = ... # type: Store.StoreType - - @property - def subscriptions(self) -> google___protobuf___internal___containers___RepeatedCompositeFieldContainer[Store.Subscription]: ... - - @property - def redis_config(self) -> Store.RedisConfig: ... - - @property - def bigquery_config(self) -> Store.BigQueryConfig: ... - - @property - def cassandra_config(self) -> Store.CassandraConfig: ... - - def __init__(self, - *, - name : typing___Optional[typing___Text] = None, - type : typing___Optional[Store.StoreType] = None, - subscriptions : typing___Optional[typing___Iterable[Store.Subscription]] = None, - redis_config : typing___Optional[Store.RedisConfig] = None, - bigquery_config : typing___Optional[Store.BigQueryConfig] = None, - cassandra_config : typing___Optional[Store.CassandraConfig] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> Store: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def HasField(self, field_name: typing_extensions___Literal[u"bigquery_config",u"cassandra_config",u"config",u"redis_config"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"bigquery_config",u"cassandra_config",u"config",u"name",u"redis_config",u"subscriptions",u"type"]) -> None: ... - else: - def HasField(self, field_name: typing_extensions___Literal[u"bigquery_config",b"bigquery_config",u"cassandra_config",b"cassandra_config",u"config",b"config",u"redis_config",b"redis_config"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"bigquery_config",b"bigquery_config",u"cassandra_config",b"cassandra_config",u"config",b"config",u"name",b"name",u"redis_config",b"redis_config",u"subscriptions",b"subscriptions",u"type",b"type"]) -> None: ... - def WhichOneof(self, oneof_group: typing_extensions___Literal[u"config",b"config"]) -> typing_extensions___Literal["redis_config","bigquery_config","cassandra_config"]: ... diff --git a/sdk/python/feast/core/Store_pb2_grpc.py b/sdk/python/feast/core/Store_pb2_grpc.py deleted file mode 100644 index a89435267cb..00000000000 --- a/sdk/python/feast/core/Store_pb2_grpc.py +++ /dev/null @@ -1,3 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -import grpc - diff --git a/sdk/python/feast/entity.py b/sdk/python/feast/entity.py index 795758bc417..012d01631af 100644 --- a/sdk/python/feast/entity.py +++ b/sdk/python/feast/entity.py @@ -12,10 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -from feast.value_type import ValueType from feast.core.FeatureSet_pb2 import EntitySpec as EntityProto -from feast.types import Value_pb2 as ValueTypeProto from feast.field import Field +from feast.types import Value_pb2 as ValueTypeProto +from feast.value_type import ValueType class Entity(Field): @@ -29,7 +29,7 @@ def to_proto(self) -> EntityProto: Returns EntitySpec object """ value_type = ValueTypeProto.ValueType.Enum.Value(self.dtype.name) - return EntityProto(name=self.name, value_type=value_type) + return EntityProto(name=self.name, value_type=value_type,) @classmethod def from_proto(cls, entity_proto: EntityProto): @@ -42,4 +42,5 @@ def from_proto(cls, entity_proto: EntityProto): Returns: Entity object """ - return cls(name=entity_proto.name, dtype=ValueType(entity_proto.value_type)) + entity = cls(name=entity_proto.name, dtype=ValueType(entity_proto.value_type)) + return entity diff --git a/sdk/python/feast/feature.py b/sdk/python/feast/feature.py index c7e3d7af8b2..35e0ea32aeb 100644 --- a/sdk/python/feast/feature.py +++ b/sdk/python/feast/feature.py @@ -12,10 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -from feast.value_type import ValueType from feast.core.FeatureSet_pb2 import FeatureSpec as FeatureProto -from feast.types import Value_pb2 as ValueTypeProto from feast.field import Field +from feast.serving.ServingService_pb2 import FeatureReference as FeatureRefProto +from feast.types import Value_pb2 as ValueTypeProto +from feast.value_type import ValueType class Feature(Field): @@ -24,9 +25,124 @@ class Feature(Field): def to_proto(self) -> FeatureProto: """Converts Feature object to its Protocol Buffer representation""" value_type = ValueTypeProto.ValueType.Enum.Value(self.dtype.name) - return FeatureProto(name=self.name, value_type=value_type) + return FeatureProto( + name=self.name, + value_type=value_type, + presence=self.presence, + group_presence=self.group_presence, + shape=self.shape, + value_count=self.value_count, + domain=self.domain, + int_domain=self.int_domain, + float_domain=self.float_domain, + string_domain=self.string_domain, + bool_domain=self.bool_domain, + struct_domain=self.struct_domain, + natural_language_domain=self.natural_language_domain, + image_domain=self.image_domain, + mid_domain=self.mid_domain, + url_domain=self.url_domain, + time_domain=self.time_domain, + time_of_day_domain=self.time_of_day_domain, + ) @classmethod def from_proto(cls, feature_proto: FeatureProto): - """Converts Protobuf Feature to its SDK equivalent""" - return cls(name=feature_proto.name, dtype=ValueType(feature_proto.value_type)) + """ + + Args: + feature_proto: FeatureSpec protobuf object + + Returns: + Feature object + """ + feature = cls( + name=feature_proto.name, dtype=ValueType(feature_proto.value_type), + ) + feature.update_presence_constraints(feature_proto) + feature.update_shape_type(feature_proto) + feature.update_domain_info(feature_proto) + return feature + + +class FeatureRef: + """ Feature Reference represents a reference to a specific feature. """ + + def __init__(self, name: str, feature_set: str = None): + self.proto = FeatureRefProto(name=name, feature_set=feature_set) + + @classmethod + def from_proto(cls, proto: FeatureRefProto): + """ + Construct a feature reference from the given FeatureReference proto + + Arg: + proto: Protobuf FeatureReference to construct from + + Returns: + FeatureRef that refers to the given feature + """ + return cls(name=proto.name, feature_set=proto.feature_set) + + @classmethod + def from_str(cls, feature_ref_str: str, ignore_project: bool = False): + """ + Parse the given string feature reference into FeatureRef model + String feature reference should be in the format feature_set:feature. + Where "feature_set" and "name" are the feature_set name and feature name + respectively. + + Args: + feature_ref_str: String representation of the feature reference + ignore_project: Ignore projects in given string feature reference + instead throwing an error + + Returns: + FeatureRef that refers to the given feature + """ + proto = FeatureRefProto() + if "/" in feature_ref_str: + if ignore_project: + _, feature_ref_str = feature_ref_str.split("/") + else: + raise ValueError(f"Unsupported feature reference: {feature_ref_str}") + + # parse feature set name if specified + if ":" in feature_ref_str: + proto.feature_set, feature_ref_str = feature_ref_str.split(":") + + proto.name = feature_ref_str + return cls.from_proto(proto) + + def to_proto(self) -> FeatureRefProto: + """ + Convert and return this feature set reference to protobuf. + + Returns: + Protobuf respresentation of this feature set reference. + """ + return self.proto + + def __repr__(self): + # return string representation of the reference + # [project/][feature_set:]name + # in protov3 unset string and int fields default to "" and 0 + ref_str = "" + if len(self.proto.project) > 0: + ref_str += self.proto.project + "/" + if len(self.proto.feature_set) > 0: + ref_str += self.proto.feature_set + ":" + ref_str += self.proto.name + return ref_str + + def __str__(self): + # human readable string of the reference + return f"FeatureRef<{self.__repr__()}>" + + def __eq__(self, other): + # compare with other feature set + return hash(self) == hash(other) + + def __hash__(self): + # hash this reference + return hash(repr(self)) diff --git a/sdk/python/feast/feature_set.py b/sdk/python/feast/feature_set.py index d5576607513..aebee52ca42 100644 --- a/sdk/python/feast/feature_set.py +++ b/sdk/python/feast/feature_set.py @@ -11,33 +11,35 @@ # 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. - - +import warnings from collections import OrderedDict -from typing import Dict -from typing import List, Optional +from typing import Dict, List, Optional import pandas as pd import pyarrow as pa +from google.protobuf import json_format +from google.protobuf.duration_pb2 import Duration +from google.protobuf.json_format import MessageToJson +from google.protobuf.message import Message +from pandas.api.types import is_datetime64_ns_dtype +from pyarrow.lib import TimestampType + from feast.core.FeatureSet_pb2 import FeatureSet as FeatureSetProto from feast.core.FeatureSet_pb2 import FeatureSetMeta as FeatureSetMetaProto from feast.core.FeatureSet_pb2 import FeatureSetSpec as FeatureSetSpecProto +from feast.core.FeatureSetReference_pb2 import ( + FeatureSetReference as FeatureSetReferenceProto, +) from feast.entity import Entity from feast.feature import Feature, Field from feast.loaders import yaml as feast_yaml from feast.source import Source -from feast.type_map import DATETIME_COLUMN -from feast.type_map import pa_to_feast_value_type -from feast.type_map import python_type_to_feast_value_type -from google.protobuf import json_format -from feast.core.FeatureSet_pb2 import FeatureSetSpec as FeatureSetSpecProto -from feast.core.FeatureSet_pb2 import FeatureSetMeta as FeatureSetMetaProto -from feast.core.FeatureSet_pb2 import FeatureSet as FeatureSetProto -from google.protobuf.duration_pb2 import Duration -from feast.type_map import python_type_to_feast_value_type -from google.protobuf.json_format import MessageToJson -from pandas.api.types import is_datetime64_ns_dtype -from pyarrow.lib import TimestampType +from feast.type_map import ( + DATETIME_COLUMN, + pa_to_feast_value_type, + python_type_to_feast_value_type, +) +from tensorflow_metadata.proto.v0 import schema_pb2 class FeatureSet: @@ -66,7 +68,6 @@ def __init__( else: self._source = source self._max_age = max_age - self._version = None self._status = None self._created_timestamp = None @@ -90,14 +91,7 @@ def __str__(self): return str(MessageToJson(self.to_proto())) def __repr__(self): - ref = "" - if self.project: - ref += self.project + "/" - if self.name: - ref += self.name - if self.version: - ref += ":" + str(self.version).strip() - return ref + return FeatureSetRef.from_feature_set(self).__repr__() @property def fields(self) -> Dict[str, Field]: @@ -200,20 +194,6 @@ def source(self, source: Source): """ self._source = source - @property - def version(self): - """ - Returns the version of this feature set - """ - return self._version - - @version.setter - def version(self, version): - """ - Sets the version of this feature set - """ - self._version = version - @property def max_age(self): """ @@ -626,7 +606,6 @@ def _update_from_feature_set(self, feature_set): self.name = feature_set.name self.project = feature_set.project - self.version = feature_set.version self.source = feature_set.source self.max_age = feature_set.max_age self.features = feature_set.features @@ -639,7 +618,7 @@ def get_kafka_source_brokers(self) -> str: """ Get the broker list for the source in this feature set """ - if self.source and self.source.source_type is "Kafka": + if self.source and self.source.source_type == "Kafka": return self.source.brokers raise Exception("Source type could not be identified") @@ -658,10 +637,99 @@ def is_valid(self): """ if not self.name: - raise ValueError(f"No name found in feature set.") + raise ValueError("No name found in feature set.") if len(self.entities) == 0: - raise ValueError(f"No entities found in feature set {self.name}") + raise ValueError("No entities found in feature set {self.name}") + + def import_tfx_schema(self, schema: schema_pb2.Schema): + """ + Updates presence_constraints, shape_type and domain_info for all fields + (features and entities) in the FeatureSet from schema in the Tensorflow metadata. + + Args: + schema: Schema from Tensorflow metadata + + Returns: + None + + """ + _make_tfx_schema_domain_info_inline(schema) + for feature_from_tfx_schema in schema.feature: + if feature_from_tfx_schema.name in self._fields.keys(): + field = self._fields[feature_from_tfx_schema.name] + field.update_presence_constraints(feature_from_tfx_schema) + field.update_shape_type(feature_from_tfx_schema) + field.update_domain_info(feature_from_tfx_schema) + else: + warnings.warn( + f"The provided schema contains feature name '{feature_from_tfx_schema.name}' " + f"that does not exist in the FeatureSet '{self.name}' in Feast" + ) + + def export_tfx_schema(self) -> schema_pb2.Schema: + """ + Create a Tensorflow metadata schema from a FeatureSet. + + Returns: + Tensorflow metadata schema. + + """ + schema = schema_pb2.Schema() + + # List of attributes to copy from fields in the FeatureSet to feature in + # Tensorflow metadata schema where the attribute name is the same. + attributes_to_copy_from_field_to_feature = [ + "name", + "presence", + "group_presence", + "shape", + "value_count", + "domain", + "int_domain", + "float_domain", + "string_domain", + "bool_domain", + "struct_domain", + "_natural_language_domain", + "image_domain", + "mid_domain", + "url_domain", + "time_domain", + "time_of_day_domain", + ] + + for _, field in self._fields.items(): + if isinstance(field, Entity): + continue + feature = schema_pb2.Feature() + for attr in attributes_to_copy_from_field_to_feature: + if getattr(field, attr) is None: + # This corresponds to an unset member in the proto Oneof field. + continue + if issubclass(type(getattr(feature, attr)), Message): + # Proto message field to copy is an "embedded" field, so MergeFrom() + # method must be used. + getattr(feature, attr).MergeFrom(getattr(field, attr)) + elif issubclass(type(getattr(feature, attr)), (int, str, bool)): + # Proto message field is a simple Python type, so setattr() + # can be used. + setattr(feature, attr, getattr(field, attr)) + else: + warnings.warn( + f"Attribute '{attr}' cannot be copied from Field " + f"'{field.name}' in FeatureSet '{self.name}' to a " + f"Feature in the Tensorflow metadata schema, because" + f"the type is neither a Protobuf message or Python " + f"int, str and bool" + ) + # "type" attr is handled separately because the attribute name is different + # ("dtype" in field and "type" in Feature) and "type" in Feature is only + # a subset of "dtype". + feature.type = field.dtype.to_tfx_schema_feature_type() + schema.feature.append(feature) + + return schema @classmethod def from_yaml(cls, yml: str): @@ -689,8 +757,6 @@ def from_dict(cls, fs_dict): Returns a FeatureSet object based on the feature set dict """ - if ("kind" not in fs_dict) and (fs_dict["kind"].strip() != "feature_set"): - raise Exception(f"Resource kind is not a feature set {str(fs_dict)}") feature_set_proto = json_format.ParseDict( fs_dict, FeatureSetProto(), ignore_unknown_fields=True ) @@ -727,7 +793,6 @@ def from_proto(cls, feature_set_proto: FeatureSetProto): if len(feature_set_proto.spec.project) == 0 else feature_set_proto.spec.project, ) - feature_set._version = feature_set_proto.spec.version feature_set._status = feature_set_proto.meta.status feature_set._created_timestamp = feature_set_proto.meta.created_timestamp return feature_set @@ -746,7 +811,6 @@ def to_proto(self) -> FeatureSetProto: spec = FeatureSetSpecProto( name=self.name, - version=self.version, project=self.project, max_age=self.max_age, source=self.source.to_proto() if self.source is not None else None, @@ -765,6 +829,125 @@ def to_proto(self) -> FeatureSetProto: return FeatureSetProto(spec=spec, meta=meta) +class FeatureSetRef: + """ + Represents a reference to a featureset + """ + + def __init__(self, project: str = None, name: str = None): + self.proto = FeatureSetReferenceProto(project=project, name=name) + + @property + def project(self) -> str: + """ + Get the project of feature set referenced by this reference + """ + return self.proto.project + + @property + def name(self) -> str: + """ + Get the name of feature set referenced by this reference + """ + return self.proto.name + + @classmethod + def from_feature_set(cls, feature_set: FeatureSet): + """ + Construct a feature set reference that refers to the given feature set. + + Args: + feature_set: Feature set to create reference from. + + Returns: + FeatureSetRef that refers to the given feature set + """ + return cls(feature_set.project, feature_set.name) + + @classmethod + def from_str(cls, ref_str: str): + """ + Parse a feature reference from string representation. + (as defined by __repr__()) + + Args: + ref_str: string representation of the reference. + + Returns: + FeatureSetRef constructed from the string + """ + project = "" + if "/" in ref_str: + project, ref_str = ref_str.split("/") + + return cls(project, ref_str) + + def to_proto(self) -> FeatureSetReferenceProto: + """ + Convert and return this feature set reference to protobuf. + + Returns: + Protobuf version of this feature set reference. + """ + return self.proto + + def __str__(self): + # human readable string of the reference + return f"FeatureSetRef<{self.__repr__()}>" + + def __repr__(self): + # return string representation of the reference + # [project/]name + # in protov3 unset string and int fields default to "" and 0 + ref_str = "" + if len(self.proto.project) > 0: + ref_str += self.proto.project + "/" + ref_str += self.proto.name + return ref_str + + def __eq__(self, other): + # compare with other feature set + return hash(self) == hash(other) + + def __hash__(self): + # hash this reference + return hash(repr(self)) + + +def _make_tfx_schema_domain_info_inline(schema: schema_pb2.Schema) -> None: + """ + Copy top level domain info defined at schema level into inline definition. + One use case is when importing domain info from Tensorflow metadata schema + into Feast features. Feast features do not have access to schema level information + so the domain info needs to be inline. + + Args: + schema: Tensorflow metadata schema + + Returns: None + """ + # Reference to domains defined at schema level + domain_ref_to_string_domain = {d.name: d for d in schema.string_domain} + domain_ref_to_float_domain = {d.name: d for d in schema.float_domain} + domain_ref_to_int_domain = {d.name: d for d in schema.int_domain} + + # With the reference, it is safe to remove the domains defined at schema level + del schema.string_domain[:] + del schema.float_domain[:] + del schema.int_domain[:] + + for feature in schema.feature: + domain_info_case = feature.WhichOneof("domain_info") + if domain_info_case == "domain": + domain_ref = feature.domain + if domain_ref in domain_ref_to_string_domain: + feature.string_domain.MergeFrom(domain_ref_to_string_domain[domain_ref]) + elif domain_ref in domain_ref_to_float_domain: + feature.float_domain.MergeFrom(domain_ref_to_float_domain[domain_ref]) + elif domain_ref in domain_ref_to_int_domain: + feature.int_domain.MergeFrom(domain_ref_to_int_domain[domain_ref]) + + def _infer_pd_column_type(column, series, rows_to_sample): dtype = None sample_count = 0 diff --git a/sdk/python/feast/field.py b/sdk/python/feast/field.py index 2efd4587ff0..be56823489b 100644 --- a/sdk/python/feast/field.py +++ b/sdk/python/feast/field.py @@ -11,8 +11,11 @@ # 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. +from typing import Union +from feast.core.FeatureSet_pb2 import EntitySpec, FeatureSpec from feast.value_type import ValueType +from tensorflow_metadata.proto.v0 import schema_pb2 class Field: @@ -26,6 +29,22 @@ def __init__(self, name: str, dtype: ValueType): if not isinstance(dtype, ValueType): raise ValueError("dtype is not a valid ValueType") self._dtype = dtype + self._presence = None + self._group_presence = None + self._shape = None + self._value_count = None + self._domain = None + self._int_domain = None + self._float_domain = None + self._string_domain = None + self._bool_domain = None + self._struct_domain = None + self._natural_language_domain = None + self._image_domain = None + self._mid_domain = None + self._url_domain = None + self._time_domain = None + self._time_of_day_domain = None def __eq__(self, other): if self.name != other.name or self.dtype != other.dtype: @@ -46,6 +65,354 @@ def dtype(self) -> ValueType: """ return self._dtype + @property + def presence(self) -> schema_pb2.FeaturePresence: + """ + Getter for presence of this field + """ + return self._presence + + @presence.setter + def presence(self, presence: schema_pb2.FeaturePresence): + """ + Setter for presence of this field + """ + if not isinstance(presence, schema_pb2.FeaturePresence): + raise TypeError("presence must be of FeaturePresence type") + self._clear_presence_constraints() + self._presence = presence + + @property + def group_presence(self) -> schema_pb2.FeaturePresenceWithinGroup: + """ + Getter for group_presence of this field + """ + return self._group_presence + + @group_presence.setter + def group_presence(self, group_presence: schema_pb2.FeaturePresenceWithinGroup): + """ + Setter for group_presence of this field + """ + if not isinstance(group_presence, schema_pb2.FeaturePresenceWithinGroup): + raise TypeError("group_presence must be of FeaturePresenceWithinGroup type") + self._clear_presence_constraints() + self._group_presence = group_presence + + @property + def shape(self) -> schema_pb2.FixedShape: + """ + Getter for shape of this field + """ + return self._shape + + @shape.setter + def shape(self, shape: schema_pb2.FixedShape): + """ + Setter for shape of this field + """ + if not isinstance(shape, schema_pb2.FixedShape): + raise TypeError("shape must be of FixedShape type") + self._clear_shape_type() + self._shape = shape + + @property + def value_count(self) -> schema_pb2.ValueCount: + """ + Getter for value_count of this field + """ + return self._value_count + + @value_count.setter + def value_count(self, value_count: schema_pb2.ValueCount): + """ + Setter for value_count of this field + """ + if not isinstance(value_count, schema_pb2.ValueCount): + raise TypeError("value_count must be of ValueCount type") + self._clear_shape_type() + self._value_count = value_count + + @property + def domain(self) -> str: + """ + Getter for domain of this field + """ + return self._domain + + @domain.setter + def domain(self, domain: str): + """ + Setter for domain of this field + """ + if not isinstance(domain, str): + raise TypeError("domain must be of str type") + self._clear_domain_info() + self._domain = domain + + @property + def int_domain(self) -> schema_pb2.IntDomain: + """ + Getter for int_domain of this field + """ + return self._int_domain + + @int_domain.setter + def int_domain(self, int_domain: schema_pb2.IntDomain): + """ + Setter for int_domain of this field + """ + if not isinstance(int_domain, schema_pb2.IntDomain): + raise TypeError("int_domain must be of IntDomain type") + self._clear_domain_info() + self._int_domain = int_domain + + @property + def float_domain(self) -> schema_pb2.FloatDomain: + """ + Getter for float_domain of this field + """ + return self._float_domain + + @float_domain.setter + def float_domain(self, float_domain: schema_pb2.FloatDomain): + """ + Setter for float_domain of this field + """ + if not isinstance(float_domain, schema_pb2.FloatDomain): + raise TypeError("float_domain must be of FloatDomain type") + self._clear_domain_info() + self._float_domain = float_domain + + @property + def string_domain(self) -> schema_pb2.StringDomain: + """ + Getter for string_domain of this field + """ + return self._string_domain + + @string_domain.setter + def string_domain(self, string_domain: schema_pb2.StringDomain): + """ + Setter for string_domain of this field + """ + if not isinstance(string_domain, schema_pb2.StringDomain): + raise TypeError("string_domain must be of StringDomain type") + self._clear_domain_info() + self._string_domain = string_domain + + @property + def bool_domain(self) -> schema_pb2.BoolDomain: + """ + Getter for bool_domain of this field + """ + return self._bool_domain + + @bool_domain.setter + def bool_domain(self, bool_domain: schema_pb2.BoolDomain): + """ + Setter for bool_domain of this field + """ + if not isinstance(bool_domain, schema_pb2.BoolDomain): + raise TypeError("bool_domain must be of BoolDomain type") + self._clear_domain_info() + self._bool_domain = bool_domain + + @property + def struct_domain(self) -> schema_pb2.StructDomain: + """ + Getter for struct_domain of this field + """ + return self._struct_domain + + @struct_domain.setter + def struct_domain(self, struct_domain: schema_pb2.StructDomain): + """ + Setter for struct_domain of this field + """ + if not isinstance(struct_domain, schema_pb2.StructDomain): + raise TypeError("struct_domain must be of StructDomain type") + self._clear_domain_info() + self._struct_domain = struct_domain + + @property + def natural_language_domain(self) -> schema_pb2.NaturalLanguageDomain: + """ + Getter for natural_language_domain of this field + """ + return self._natural_language_domain + + @natural_language_domain.setter + def natural_language_domain( + self, natural_language_domain: schema_pb2.NaturalLanguageDomain + ): + """ + Setter for natural_language_domin of this field + """ + if not isinstance(natural_language_domain, schema_pb2.NaturalLanguageDomain): + raise TypeError( + "natural_language_domain must be of NaturalLanguageDomain type" + ) + self._clear_domain_info() + self._natural_language_domain = natural_language_domain + + @property + def image_domain(self) -> schema_pb2.ImageDomain: + """ + Getter for image_domain of this field + """ + return self._image_domain + + @image_domain.setter + def image_domain(self, image_domain: schema_pb2.ImageDomain): + """ + Setter for image_domain of this field + """ + if not isinstance(image_domain, schema_pb2.ImageDomain): + raise TypeError("image_domain must be of ImageDomain type") + self._clear_domain_info() + self._image_domain = image_domain + + @property + def mid_domain(self) -> schema_pb2.MIDDomain: + """ + Getter for mid_domain of this field + """ + return self._mid_domain + + @mid_domain.setter + def mid_domain(self, mid_domain: schema_pb2.MIDDomain): + """ + Setter for mid_domain of this field + """ + if not isinstance(mid_domain, schema_pb2.MIDDomain): + raise TypeError("mid_domain must be of MIDDomain type") + self._clear_domain_info() + self._mid_domain = mid_domain + + @property + def url_domain(self) -> schema_pb2.URLDomain: + """ + Getter for url_domain of this field + """ + return self._url_domain + + @url_domain.setter + def url_domain(self, url_domain: schema_pb2.URLDomain): + """ + Setter for url_domain of this field + """ + if not isinstance(url_domain, schema_pb2.URLDomain): + raise TypeError("url_domain must be of URLDomain type") + self._clear_domain_info() + self.url_domain = url_domain + + @property + def time_domain(self) -> schema_pb2.TimeDomain: + """ + Getter for time_domain of this field + """ + return self._time_domain + + @time_domain.setter + def time_domain(self, time_domain: schema_pb2.TimeDomain): + """ + Setter for time_domain of this field + """ + if not isinstance(time_domain, schema_pb2.TimeDomain): + raise TypeError("time_domain must be of TimeDomain type") + self._clear_domain_info() + self._time_domain = time_domain + + @property + def time_of_day_domain(self) -> schema_pb2.TimeOfDayDomain: + """ + Getter for time_of_day_domain of this field + """ + return self._time_of_day_domain + + @time_of_day_domain.setter + def time_of_day_domain(self, time_of_day_domain): + """ + Setter for time_of_day_domain of this field + """ + if not isinstance(time_of_day_domain, schema_pb2.TimeOfDayDomain): + raise TypeError("time_of_day_domain must be of TimeOfDayDomain type") + self._clear_domain_info() + self._time_of_day_domain = time_of_day_domain + + def update_presence_constraints( + self, feature: Union[schema_pb2.Feature, EntitySpec, FeatureSpec] + ) -> None: + """ + Update the presence constraints in this field from Tensorflow Feature, + Feast EntitySpec or FeatureSpec + + Args: + feature: Tensorflow Feature, Feast EntitySpec or FeatureSpec + + Returns: None + """ + presence_constraints_case = feature.WhichOneof("presence_constraints") + if presence_constraints_case == "presence": + self.presence = feature.presence + elif presence_constraints_case == "group_presence": + self.group_presence = feature.group_presence + + def update_shape_type( + self, feature: Union[schema_pb2.Feature, EntitySpec, FeatureSpec] + ) -> None: + """ + Update the shape type in this field from Tensorflow Feature, + Feast EntitySpec or FeatureSpec + + Args: + feature: Tensorflow Feature, Feast EntitySpec or FeatureSpec + + Returns: None + """ + shape_type_case = feature.WhichOneof("shape_type") + if shape_type_case == "shape": + self.shape = feature.shape + elif shape_type_case == "value_count": + self.value_count = feature.value_count + + def update_domain_info( + self, feature: Union[schema_pb2.Feature, EntitySpec, FeatureSpec] + ) -> None: + """ + Update the domain info in this field from Tensorflow Feature, Feast EntitySpec + or FeatureSpec + + Args: + feature: Tensorflow Feature, Feast EntitySpec or FeatureSpec + + Returns: None + """ + domain_info_case = feature.WhichOneof("domain_info") + if domain_info_case == "int_domain": + self.int_domain = feature.int_domain + elif domain_info_case == "float_domain": + self.float_domain = feature.float_domain + elif domain_info_case == "string_domain": + self.string_domain = feature.string_domain + elif domain_info_case == "bool_domain": + self.bool_domain = feature.bool_domain + elif domain_info_case == "struct_domain": + self.struct_domain = feature.struct_domain + elif domain_info_case == "natural_language_domain": + self.natural_language_domain = feature.natural_language_domain + elif domain_info_case == "image_domain": + self.image_domain = feature.image_domain + elif domain_info_case == "mid_domain": + self.mid_domain = feature.mid_domain + elif domain_info_case == "url_domain": + self.url_domain = feature.url_domain + elif domain_info_case == "time_domain": + self.time_domain = feature.time_domain + elif domain_info_case == "time_of_day_domain": + self.time_of_day_domain = feature.time_of_day_domain + def to_proto(self): """ Unimplemented to_proto method for a field. This should be extended. @@ -57,3 +424,25 @@ def from_proto(self, proto): Unimplemented from_proto method for a field. This should be extended. """ pass + + def _clear_presence_constraints(self): + self._presence = None + self._group_presence = None + + def _clear_shape_type(self): + self._shape = None + self._value_count = None + + def _clear_domain_info(self): + self._domain = None + self._int_domain = None + self._float_domain = None + self._string_domain = None + self._bool_domain = None + self._struct_domain = None + self._natural_language_domain = None + self._image_domain = None + self._mid_domain = None + self._url_domain = None + self._time_domain = None + self._time_of_day_domain = None diff --git a/sdk/python/feast/job.py b/sdk/python/feast/job.py index f849f6630da..21b08224bad 100644 --- a/sdk/python/feast/job.py +++ b/sdk/python/feast/job.py @@ -1,22 +1,30 @@ import tempfile import time from datetime import datetime, timedelta -from typing import Iterable +from typing import List from urllib.parse import urlparse import fastavro import pandas as pd from google.cloud import storage - -from feast.serving.ServingService_pb2 import GetJobRequest +from google.protobuf.json_format import MessageToJson + +from feast.core.CoreService_pb2 import ListIngestionJobsRequest +from feast.core.CoreService_pb2_grpc import CoreServiceStub +from feast.core.IngestionJob_pb2 import IngestionJob as IngestJobProto +from feast.core.IngestionJob_pb2 import IngestionJobStatus +from feast.core.Store_pb2 import Store +from feast.feature_set import FeatureSet from feast.serving.ServingService_pb2 import ( - Job as JobProto, - JOB_STATUS_DONE, DATA_FORMAT_AVRO, + JOB_STATUS_DONE, + GetJobRequest, ) +from feast.serving.ServingService_pb2 import Job as JobProto from feast.serving.ServingService_pb2_grpc import ServingServiceStub +from feast.source import Source -# Maximum no of seconds to wait until the jobs status is DONE in Feast +# Maximum no of seconds to wait until the retrieval jobs status is DONE in Feast # Currently set to the maximum query execution time limit in BigQuery DEFAULT_TIMEOUT_SEC: int = 21600 @@ -24,21 +32,23 @@ MAX_WAIT_INTERVAL_SEC: int = 60 -class Job: +class RetrievalJob: """ A class representing a job for feature retrieval in Feast. """ - def __init__(self, job_proto: JobProto, serving_stub: ServingServiceStub): + def __init__( + self, job_proto: JobProto, serving_stub: ServingServiceStub, + ): """ Args: job_proto: Job proto object (wrapped by this job object) serving_stub: Stub for Feast serving service - storage_client: Google Cloud Storage client """ self.job_proto = job_proto self.serving_stub = serving_stub - self.storage_client = storage.Client(project=None) + # TODO: abstract away GCP depedency + self.gcs_client = storage.Client(project=None) @property def id(self): @@ -118,7 +128,7 @@ def result(self, timeout_sec: int = DEFAULT_TIMEOUT_SEC): for file_uri in uris: if file_uri.scheme == "gs": file_obj = tempfile.TemporaryFile() - self.storage_client.download_blob_to_file(file_uri.geturl(), file_obj) + self.gcs_client.download_blob_to_file(file_uri.geturl(), file_obj) elif file_uri.scheme == "file": file_obj = open(file_uri.path, "rb") else: @@ -188,3 +198,107 @@ def to_chunked_dataframe( def __iter__(self): return iter(self.result()) + + +class IngestJob: + """ + Defines a job for feature ingestion in feast. + """ + + def __init__(self, job_proto: IngestJobProto, core_stub: CoreServiceStub): + """ + Construct a native ingest job from its protobuf version. + + Args: + job_proto: Job proto object to construct from. + core_stub: stub for Feast CoreService + """ + self.proto = job_proto + self.core_svc = core_stub + + def reload(self): + """ + Update this IngestJob with the latest info from Feast + """ + # pull latest proto from feast core + response = self.core_svc.ListIngestionJobs( + ListIngestionJobsRequest(filter=ListIngestionJobsRequest.Filter(id=self.id)) + ) + self.proto = response.jobs[0] + + @property + def id(self) -> str: + """ + Getter for IngestJob's job id. + """ + return self.proto.id + + @property + def external_id(self) -> str: + """ + Getter for IngestJob's external job id. + """ + self.reload() + return self.proto.external_id + + @property + def status(self) -> IngestionJobStatus: + """ + Getter for IngestJob's status + """ + self.reload() + return self.proto.status + + @property + def feature_sets(self) -> List[FeatureSet]: + """ + Getter for the IngestJob's feature sets + """ + # convert featureset protos to native objects + return [FeatureSet.from_proto(fs) for fs in self.proto.feature_sets] + + @property + def source(self) -> Source: + """ + Getter for the IngestJob's data source. + """ + return Source.from_proto(self.proto.source) + + @property + def store(self) -> Store: + """ + Getter for the IngestJob's target feast store. + """ + return self.proto.store + + def wait(self, status: IngestionJobStatus, timeout_secs: float = 300): + """ + Wait for this IngestJob to transtion to the given status. + Raises TimeoutError if the wait operation times out. + + Args: + status: The IngestionJobStatus to wait for. + timeout_secs: Maximum seconds to wait before timing out. + """ + # poll & wait for job status to transition + wait_begin = time.time() + wait_secs = 2 + elapsed_secs = 0 + while self.status != status and elapsed_secs <= timeout_secs: + time.sleep(wait_secs) + # back off wait duration exponentially, capped at MAX_WAIT_INTERVAL_SEC + wait_secs = min(wait_secs * 2, MAX_WAIT_INTERVAL_SEC) + elapsed_secs = time.time() - wait_begin + + # raise error if timeout + if elapsed_secs > timeout_secs: + raise TimeoutError("Wait for IngestJob's status to transition timed out") + + def __str__(self): + # render the contents of ingest job as human readable string + self.reload() + return str(MessageToJson(self.proto)) + + def __repr__(self): + # render the ingest job as human readable string + return f"IngestJob<{self.id}>" diff --git a/sdk/python/feast/loaders/abstract_producer.py b/sdk/python/feast/loaders/abstract_producer.py index d0ddabf1e56..14d9bc42b7d 100644 --- a/sdk/python/feast/loaders/abstract_producer.py +++ b/sdk/python/feast/loaders/abstract_producer.py @@ -25,15 +25,13 @@ class AbstractProducer: def __init__(self, brokers: str, row_count: int, disable_progress_bar: bool): self.brokers = brokers self.row_count = row_count - self.error_count = 0 - self.last_exception = "" # Progress bar will always display average rate self.pbar = tqdm( total=row_count, unit="rows", smoothing=0, disable=disable_progress_bar ) - def produce(self, topic: str, data: str): + def produce(self, topic: str, data: bytes): message = "{} should implement a produce method".format(self.__class__.__name__) raise NotImplementedError(message) @@ -45,8 +43,7 @@ def _inc_pbar(self, meta): self.pbar.update(1) def _set_error(self, exception: str): - self.error_count += 1 - self.last_exception = exception + raise Exception(exception) def print_results(self) -> None: """ @@ -62,24 +59,7 @@ def print_results(self) -> None: print("Ingestion complete!") - failed_message = ( - "" - if self.error_count == 0 - else f"\nFail: {self.error_count / self.row_count}" - ) - - last_exception_message = ( - "" - if self.last_exception == "" - else f"\nLast exception:\n{self.last_exception}" - ) - - print( - f"\nIngestion statistics:" - f"\nSuccess: {self.pbar.n}/{self.row_count}" - f"{failed_message}" - f"{last_exception_message}" - ) + print(f"\nIngestion statistics:" f"\nSuccess: {self.pbar.n}/{self.row_count}") return None @@ -129,7 +109,10 @@ def flush(self, timeout: Optional[int]): Returns: int: Number of messages still in queue. """ - return self.producer.flush(timeout=timeout) + messages = self.producer.flush(timeout=timeout) + if messages: + raise Exception("Not all Kafka messages are successfully delivered.") + return messages def _delivery_callback(self, err: str, msg) -> None: """ @@ -200,7 +183,10 @@ def flush(self, timeout: Optional[int]): KafkaTimeoutError: failure to flush buffered records within the provided timeout """ - return self.producer.flush(timeout=timeout) + messages = self.producer.flush(timeout=timeout) + if messages: + raise Exception("Not all Kafka messages are successfully delivered.") + return messages def get_producer( @@ -227,6 +213,6 @@ def get_producer( """ try: return ConfluentProducer(brokers, row_count, disable_progress_bar) - except ImportError as e: + except ImportError: print("Unable to import confluent-kafka, falling back to kafka-python") return KafkaPythonProducer(brokers, row_count, disable_progress_bar) diff --git a/sdk/python/feast/loaders/file.py b/sdk/python/feast/loaders/file.py index bb050c07c6d..52cc8ae7dc8 100644 --- a/sdk/python/feast/loaders/file.py +++ b/sdk/python/feast/loaders/file.py @@ -19,7 +19,7 @@ import uuid from datetime import datetime from typing import List, Optional, Tuple, Union -from urllib.parse import urlparse, ParseResult +from urllib.parse import ParseResult, urlparse import pandas as pd from google.cloud import storage @@ -63,13 +63,14 @@ def export_source_to_staging_location( # Prepare Avro file to be exported to staging location if isinstance(source, pd.DataFrame): # DataFrame provided as a source + uri_path = None # type: Optional[str] if uri.scheme == "file": uri_path = uri.path - else: - uri_path = None # Remote gs staging location provided by serving - dir_path, file_name, source_path = export_dataframe_to_local(source, uri_path) + dir_path, file_name, source_path = export_dataframe_to_local( + df=source, dir_path=uri_path + ) elif urlparse(source).scheme in ["", "file"]: # Local file provided as a source dir_path = None @@ -108,7 +109,7 @@ def export_source_to_staging_location( ) # Clean up, remove local staging file - if isinstance(source, pd.DataFrame) and len(str(dir_path)) > 4: + if dir_path and isinstance(source, pd.DataFrame) and len(str(dir_path)) > 4: shutil.rmtree(dir_path) return [staging_location_uri.rstrip("/") + "/" + file_name] @@ -172,7 +173,7 @@ def upload_file_to_gcs(local_path: str, bucket: str, remote_path: str) -> None: remote_path (str): Path within GCS bucket to upload file to, includes file name. - + Returns: None: None diff --git a/sdk/python/feast/loaders/ingest.py b/sdk/python/feast/loaders/ingest.py index 95b699d0005..b439dbd3027 100644 --- a/sdk/python/feast/loaders/ingest.py +++ b/sdk/python/feast/loaders/ingest.py @@ -5,11 +5,12 @@ import pandas as pd import pyarrow.parquet as pq + from feast.constants import DATETIME_COLUMN from feast.feature_set import FeatureSet from feast.type_map import ( - pa_column_to_timestamp_proto_column, pa_column_to_proto_column, + pa_column_to_timestamp_proto_column, ) from feast.types import Field_pb2 as FieldProto from feast.types.FeatureRow_pb2 import FeatureRow @@ -24,7 +25,9 @@ KAFKA_CHUNK_PRODUCTION_TIMEOUT = 120 # type: int -def _encode_pa_tables(file: str, fs: FeatureSet, row_group_idx: int) -> List[bytes]: +def _encode_pa_tables( + file: str, feature_set: str, fields: dict, ingestion_id: str, row_group_idx: int +) -> List[bytes]: """ Helper function to encode a PyArrow table(s) read from parquet file(s) into FeatureRows. @@ -40,8 +43,14 @@ def _encode_pa_tables(file: str, fs: FeatureSet, row_group_idx: int) -> List[byt File directory of all the parquet file to encode. Parquet file must have more than one row group. - fs (feast.feature_set.FeatureSet): - FeatureSet describing parquet files. + feature_set (str): + Feature set reference in the format f"{project}/{name}". + + fields (dict[str, enum.Enum.ValueType]): + A mapping of field names to their value types. + + ingestion_id (str): + UUID unique to this ingestion job. row_group_idx(int): Row group index to read and encode into byte like FeatureRow @@ -60,12 +69,10 @@ def _encode_pa_tables(file: str, fs: FeatureSet, row_group_idx: int) -> List[byt # Preprocess the columns by converting all its values to Proto values proto_columns = { - field_name: pa_column_to_proto_column(field.dtype, table.column(field_name)) - for field_name, field in fs.fields.items() + field_name: pa_column_to_proto_column(dtype, table.column(field_name)) + for field_name, dtype in fields.items() } - feature_set = f"{fs.project}/{fs.name}:{fs.version}" - # List to store result feature_rows = [] @@ -77,7 +84,9 @@ def _encode_pa_tables(file: str, fs: FeatureSet, row_group_idx: int) -> List[byt # Iterate through the rows for row_idx in range(table.num_rows): feature_row = FeatureRow( - event_timestamp=datetime_col[row_idx], feature_set=feature_set + event_timestamp=datetime_col[row_idx], + feature_set=feature_set, + ingestion_id=ingestion_id, ) # Loop optimization declaration ext = feature_row.fields.extend @@ -93,7 +102,11 @@ def _encode_pa_tables(file: str, fs: FeatureSet, row_group_idx: int) -> List[byt def get_feature_row_chunks( - file: str, row_groups: List[int], fs: FeatureSet, max_workers: int + file: str, + row_groups: List[int], + fs: FeatureSet, + ingestion_id: str, + max_workers: int, ) -> Iterable[List[bytes]]: """ Iterator function to encode a PyArrow table read from a parquet file to @@ -111,6 +124,9 @@ def get_feature_row_chunks( fs (feast.feature_set.FeatureSet): FeatureSet describing parquet files. + ingestion_id (str): + UUID unique to this ingestion job. + max_workers (int): Maximum number of workers to spawn. @@ -119,8 +135,12 @@ def get_feature_row_chunks( Iterable list of byte encoded FeatureRow(s). """ + feature_set = f"{fs.project}/{fs.name}" + + field_map = {field.name: field.dtype for field in fs.fields.values()} + pool = Pool(max_workers) - func = partial(_encode_pa_tables, file, fs) + func = partial(_encode_pa_tables, file, feature_set, field_map, ingestion_id) for chunk in pool.imap(func, row_groups): yield chunk return diff --git a/sdk/python/feast/loaders/yaml.py b/sdk/python/feast/loaders/yaml.py index 4cbe15dfaaf..624bc47d49c 100644 --- a/sdk/python/feast/loaders/yaml.py +++ b/sdk/python/feast/loaders/yaml.py @@ -53,11 +53,12 @@ def _get_yaml_contents(yml: str) -> str: with open(yml, "r") as f: yml_content = f.read() - elif isinstance(yml, str) and "kind" in yml.lower(): + elif isinstance(yml, str): yml_content = yml else: raise Exception( - f"Invalid YAML provided. Please provide either a file path or YAML string: ${yml}" + f"Invalid YAML provided. Please provide either a file path or YAML string.\n" + f"Provided YAML: {yml}" ) return yml_content @@ -73,7 +74,4 @@ def _yaml_to_dict(yaml_string): Dictionary containing the same object """ - yaml_dict = yaml.safe_load(yaml_string) - if not isinstance(yaml_dict, dict) or not "kind" in yaml_dict: - raise Exception(f"Could not detect YAML kind from resource: ${yaml_string}") - return yaml_dict + return yaml.safe_load(yaml_string) diff --git a/sdk/python/feast/resource.py b/sdk/python/feast/resource.py deleted file mode 100644 index 17a65291667..00000000000 --- a/sdk/python/feast/resource.py +++ /dev/null @@ -1,10 +0,0 @@ -from feast.feature_set import FeatureSet - -# TODO: This factory adds no value. It should be removed asap. -class ResourceFactory: - @staticmethod - def get_resource(kind): - if kind == "feature_set": - return FeatureSet - else: - raise ValueError(kind) diff --git a/sdk/python/feast/serving/ServingService_pb2.py b/sdk/python/feast/serving/ServingService_pb2.py deleted file mode 100644 index 9d0d55f2ab4..00000000000 --- a/sdk/python/feast/serving/ServingService_pb2.py +++ /dev/null @@ -1,971 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: feast/serving/ServingService.proto - -import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) -from google.protobuf.internal import enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -from google.protobuf import duration_pb2 as google_dot_protobuf_dot_duration__pb2 -from feast.types import Value_pb2 as feast_dot_types_dot_Value__pb2 - - -DESCRIPTOR = _descriptor.FileDescriptor( - name='feast/serving/ServingService.proto', - package='feast.serving', - syntax='proto3', - serialized_options=_b('\n\rfeast.servingB\017ServingAPIProtoZ2github.com/gojek/feast/sdk/go/protos/feast/serving'), - serialized_pb=_b('\n\"feast/serving/ServingService.proto\x12\rfeast.serving\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x17\x66\x65\x61st/types/Value.proto\"\x1c\n\x1aGetFeastServingInfoRequest\"{\n\x1bGetFeastServingInfoResponse\x12\x0f\n\x07version\x18\x01 \x01(\t\x12-\n\x04type\x18\x02 \x01(\x0e\x32\x1f.feast.serving.FeastServingType\x12\x1c\n\x14job_staging_location\x18\n \x01(\t\"n\n\x10\x46\x65\x61tureReference\x12\x0f\n\x07project\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0f\n\x07version\x18\x03 \x01(\x05\x12*\n\x07max_age\x18\x04 \x01(\x0b\x32\x19.google.protobuf.Duration\"\x8e\x03\n\x18GetOnlineFeaturesRequest\x12\x31\n\x08\x66\x65\x61tures\x18\x04 \x03(\x0b\x32\x1f.feast.serving.FeatureReference\x12\x46\n\x0b\x65ntity_rows\x18\x02 \x03(\x0b\x32\x31.feast.serving.GetOnlineFeaturesRequest.EntityRow\x12!\n\x19omit_entities_in_response\x18\x03 \x01(\x08\x1a\xd3\x01\n\tEntityRow\x12\x34\n\x10\x65ntity_timestamp\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12M\n\x06\x66ields\x18\x02 \x03(\x0b\x32=.feast.serving.GetOnlineFeaturesRequest.EntityRow.FieldsEntry\x1a\x41\n\x0b\x46ieldsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.feast.types.Value:\x02\x38\x01\"\x82\x01\n\x17GetBatchFeaturesRequest\x12\x31\n\x08\x66\x65\x61tures\x18\x03 \x03(\x0b\x32\x1f.feast.serving.FeatureReference\x12\x34\n\x0e\x64\x61taset_source\x18\x02 \x01(\x0b\x32\x1c.feast.serving.DatasetSource\"\x8c\x02\n\x19GetOnlineFeaturesResponse\x12J\n\x0c\x66ield_values\x18\x01 \x03(\x0b\x32\x34.feast.serving.GetOnlineFeaturesResponse.FieldValues\x1a\xa2\x01\n\x0b\x46ieldValues\x12P\n\x06\x66ields\x18\x01 \x03(\x0b\x32@.feast.serving.GetOnlineFeaturesResponse.FieldValues.FieldsEntry\x1a\x41\n\x0b\x46ieldsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.feast.types.Value:\x02\x38\x01\";\n\x18GetBatchFeaturesResponse\x12\x1f\n\x03job\x18\x01 \x01(\x0b\x32\x12.feast.serving.Job\"0\n\rGetJobRequest\x12\x1f\n\x03job\x18\x01 \x01(\x0b\x32\x12.feast.serving.Job\"1\n\x0eGetJobResponse\x12\x1f\n\x03job\x18\x01 \x01(\x0b\x32\x12.feast.serving.Job\"\xb3\x01\n\x03Job\x12\n\n\x02id\x18\x01 \x01(\t\x12$\n\x04type\x18\x02 \x01(\x0e\x32\x16.feast.serving.JobType\x12(\n\x06status\x18\x03 \x01(\x0e\x32\x18.feast.serving.JobStatus\x12\r\n\x05\x65rror\x18\x04 \x01(\t\x12\x11\n\tfile_uris\x18\x05 \x03(\t\x12.\n\x0b\x64\x61ta_format\x18\x06 \x01(\x0e\x32\x19.feast.serving.DataFormat\"\xb2\x01\n\rDatasetSource\x12>\n\x0b\x66ile_source\x18\x01 \x01(\x0b\x32\'.feast.serving.DatasetSource.FileSourceH\x00\x1aO\n\nFileSource\x12\x11\n\tfile_uris\x18\x01 \x03(\t\x12.\n\x0b\x64\x61ta_format\x18\x02 \x01(\x0e\x32\x19.feast.serving.DataFormatB\x10\n\x0e\x64\x61taset_source*o\n\x10\x46\x65\x61stServingType\x12\x1e\n\x1a\x46\x45\x41ST_SERVING_TYPE_INVALID\x10\x00\x12\x1d\n\x19\x46\x45\x41ST_SERVING_TYPE_ONLINE\x10\x01\x12\x1c\n\x18\x46\x45\x41ST_SERVING_TYPE_BATCH\x10\x02*6\n\x07JobType\x12\x14\n\x10JOB_TYPE_INVALID\x10\x00\x12\x15\n\x11JOB_TYPE_DOWNLOAD\x10\x01*h\n\tJobStatus\x12\x16\n\x12JOB_STATUS_INVALID\x10\x00\x12\x16\n\x12JOB_STATUS_PENDING\x10\x01\x12\x16\n\x12JOB_STATUS_RUNNING\x10\x02\x12\x13\n\x0fJOB_STATUS_DONE\x10\x03*;\n\nDataFormat\x12\x17\n\x13\x44\x41TA_FORMAT_INVALID\x10\x00\x12\x14\n\x10\x44\x41TA_FORMAT_AVRO\x10\x01\x32\x92\x03\n\x0eServingService\x12l\n\x13GetFeastServingInfo\x12).feast.serving.GetFeastServingInfoRequest\x1a*.feast.serving.GetFeastServingInfoResponse\x12\x66\n\x11GetOnlineFeatures\x12\'.feast.serving.GetOnlineFeaturesRequest\x1a(.feast.serving.GetOnlineFeaturesResponse\x12\x63\n\x10GetBatchFeatures\x12&.feast.serving.GetBatchFeaturesRequest\x1a\'.feast.serving.GetBatchFeaturesResponse\x12\x45\n\x06GetJob\x12\x1c.feast.serving.GetJobRequest\x1a\x1d.feast.serving.GetJobResponseBT\n\rfeast.servingB\x0fServingAPIProtoZ2github.com/gojek/feast/sdk/go/protos/feast/servingb\x06proto3') - , - dependencies=[google_dot_protobuf_dot_timestamp__pb2.DESCRIPTOR,google_dot_protobuf_dot_duration__pb2.DESCRIPTOR,feast_dot_types_dot_Value__pb2.DESCRIPTOR,]) - -_FEASTSERVINGTYPE = _descriptor.EnumDescriptor( - name='FeastServingType', - full_name='feast.serving.FeastServingType', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='FEAST_SERVING_TYPE_INVALID', index=0, number=0, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='FEAST_SERVING_TYPE_ONLINE', index=1, number=1, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='FEAST_SERVING_TYPE_BATCH', index=2, number=2, - serialized_options=None, - type=None), - ], - containing_type=None, - serialized_options=None, - serialized_start=1740, - serialized_end=1851, -) -_sym_db.RegisterEnumDescriptor(_FEASTSERVINGTYPE) - -FeastServingType = enum_type_wrapper.EnumTypeWrapper(_FEASTSERVINGTYPE) -_JOBTYPE = _descriptor.EnumDescriptor( - name='JobType', - full_name='feast.serving.JobType', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='JOB_TYPE_INVALID', index=0, number=0, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='JOB_TYPE_DOWNLOAD', index=1, number=1, - serialized_options=None, - type=None), - ], - containing_type=None, - serialized_options=None, - serialized_start=1853, - serialized_end=1907, -) -_sym_db.RegisterEnumDescriptor(_JOBTYPE) - -JobType = enum_type_wrapper.EnumTypeWrapper(_JOBTYPE) -_JOBSTATUS = _descriptor.EnumDescriptor( - name='JobStatus', - full_name='feast.serving.JobStatus', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='JOB_STATUS_INVALID', index=0, number=0, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='JOB_STATUS_PENDING', index=1, number=1, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='JOB_STATUS_RUNNING', index=2, number=2, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='JOB_STATUS_DONE', index=3, number=3, - serialized_options=None, - type=None), - ], - containing_type=None, - serialized_options=None, - serialized_start=1909, - serialized_end=2013, -) -_sym_db.RegisterEnumDescriptor(_JOBSTATUS) - -JobStatus = enum_type_wrapper.EnumTypeWrapper(_JOBSTATUS) -_DATAFORMAT = _descriptor.EnumDescriptor( - name='DataFormat', - full_name='feast.serving.DataFormat', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='DATA_FORMAT_INVALID', index=0, number=0, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='DATA_FORMAT_AVRO', index=1, number=1, - serialized_options=None, - type=None), - ], - containing_type=None, - serialized_options=None, - serialized_start=2015, - serialized_end=2074, -) -_sym_db.RegisterEnumDescriptor(_DATAFORMAT) - -DataFormat = enum_type_wrapper.EnumTypeWrapper(_DATAFORMAT) -FEAST_SERVING_TYPE_INVALID = 0 -FEAST_SERVING_TYPE_ONLINE = 1 -FEAST_SERVING_TYPE_BATCH = 2 -JOB_TYPE_INVALID = 0 -JOB_TYPE_DOWNLOAD = 1 -JOB_STATUS_INVALID = 0 -JOB_STATUS_PENDING = 1 -JOB_STATUS_RUNNING = 2 -JOB_STATUS_DONE = 3 -DATA_FORMAT_INVALID = 0 -DATA_FORMAT_AVRO = 1 - - - -_GETFEASTSERVINGINFOREQUEST = _descriptor.Descriptor( - name='GetFeastServingInfoRequest', - full_name='feast.serving.GetFeastServingInfoRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=143, - serialized_end=171, -) - - -_GETFEASTSERVINGINFORESPONSE = _descriptor.Descriptor( - name='GetFeastServingInfoResponse', - full_name='feast.serving.GetFeastServingInfoResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='version', full_name='feast.serving.GetFeastServingInfoResponse.version', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='type', full_name='feast.serving.GetFeastServingInfoResponse.type', index=1, - number=2, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='job_staging_location', full_name='feast.serving.GetFeastServingInfoResponse.job_staging_location', index=2, - number=10, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=173, - serialized_end=296, -) - - -_FEATUREREFERENCE = _descriptor.Descriptor( - name='FeatureReference', - full_name='feast.serving.FeatureReference', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='project', full_name='feast.serving.FeatureReference.project', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='name', full_name='feast.serving.FeatureReference.name', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='version', full_name='feast.serving.FeatureReference.version', index=2, - number=3, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='max_age', full_name='feast.serving.FeatureReference.max_age', index=3, - number=4, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=298, - serialized_end=408, -) - - -_GETONLINEFEATURESREQUEST_ENTITYROW_FIELDSENTRY = _descriptor.Descriptor( - name='FieldsEntry', - full_name='feast.serving.GetOnlineFeaturesRequest.EntityRow.FieldsEntry', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='feast.serving.GetOnlineFeaturesRequest.EntityRow.FieldsEntry.key', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='value', full_name='feast.serving.GetOnlineFeaturesRequest.EntityRow.FieldsEntry.value', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=_b('8\001'), - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=744, - serialized_end=809, -) - -_GETONLINEFEATURESREQUEST_ENTITYROW = _descriptor.Descriptor( - name='EntityRow', - full_name='feast.serving.GetOnlineFeaturesRequest.EntityRow', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='entity_timestamp', full_name='feast.serving.GetOnlineFeaturesRequest.EntityRow.entity_timestamp', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='fields', full_name='feast.serving.GetOnlineFeaturesRequest.EntityRow.fields', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[_GETONLINEFEATURESREQUEST_ENTITYROW_FIELDSENTRY, ], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=598, - serialized_end=809, -) - -_GETONLINEFEATURESREQUEST = _descriptor.Descriptor( - name='GetOnlineFeaturesRequest', - full_name='feast.serving.GetOnlineFeaturesRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='features', full_name='feast.serving.GetOnlineFeaturesRequest.features', index=0, - number=4, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='entity_rows', full_name='feast.serving.GetOnlineFeaturesRequest.entity_rows', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='omit_entities_in_response', full_name='feast.serving.GetOnlineFeaturesRequest.omit_entities_in_response', index=2, - number=3, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[_GETONLINEFEATURESREQUEST_ENTITYROW, ], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=411, - serialized_end=809, -) - - -_GETBATCHFEATURESREQUEST = _descriptor.Descriptor( - name='GetBatchFeaturesRequest', - full_name='feast.serving.GetBatchFeaturesRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='features', full_name='feast.serving.GetBatchFeaturesRequest.features', index=0, - number=3, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='dataset_source', full_name='feast.serving.GetBatchFeaturesRequest.dataset_source', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=812, - serialized_end=942, -) - - -_GETONLINEFEATURESRESPONSE_FIELDVALUES_FIELDSENTRY = _descriptor.Descriptor( - name='FieldsEntry', - full_name='feast.serving.GetOnlineFeaturesResponse.FieldValues.FieldsEntry', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='feast.serving.GetOnlineFeaturesResponse.FieldValues.FieldsEntry.key', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='value', full_name='feast.serving.GetOnlineFeaturesResponse.FieldValues.FieldsEntry.value', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=_b('8\001'), - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=744, - serialized_end=809, -) - -_GETONLINEFEATURESRESPONSE_FIELDVALUES = _descriptor.Descriptor( - name='FieldValues', - full_name='feast.serving.GetOnlineFeaturesResponse.FieldValues', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='fields', full_name='feast.serving.GetOnlineFeaturesResponse.FieldValues.fields', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[_GETONLINEFEATURESRESPONSE_FIELDVALUES_FIELDSENTRY, ], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1051, - serialized_end=1213, -) - -_GETONLINEFEATURESRESPONSE = _descriptor.Descriptor( - name='GetOnlineFeaturesResponse', - full_name='feast.serving.GetOnlineFeaturesResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='field_values', full_name='feast.serving.GetOnlineFeaturesResponse.field_values', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[_GETONLINEFEATURESRESPONSE_FIELDVALUES, ], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=945, - serialized_end=1213, -) - - -_GETBATCHFEATURESRESPONSE = _descriptor.Descriptor( - name='GetBatchFeaturesResponse', - full_name='feast.serving.GetBatchFeaturesResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='job', full_name='feast.serving.GetBatchFeaturesResponse.job', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1215, - serialized_end=1274, -) - - -_GETJOBREQUEST = _descriptor.Descriptor( - name='GetJobRequest', - full_name='feast.serving.GetJobRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='job', full_name='feast.serving.GetJobRequest.job', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1276, - serialized_end=1324, -) - - -_GETJOBRESPONSE = _descriptor.Descriptor( - name='GetJobResponse', - full_name='feast.serving.GetJobResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='job', full_name='feast.serving.GetJobResponse.job', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1326, - serialized_end=1375, -) - - -_JOB = _descriptor.Descriptor( - name='Job', - full_name='feast.serving.Job', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='id', full_name='feast.serving.Job.id', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='type', full_name='feast.serving.Job.type', index=1, - number=2, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='status', full_name='feast.serving.Job.status', index=2, - number=3, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='error', full_name='feast.serving.Job.error', index=3, - number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='file_uris', full_name='feast.serving.Job.file_uris', index=4, - number=5, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='data_format', full_name='feast.serving.Job.data_format', index=5, - number=6, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1378, - serialized_end=1557, -) - - -_DATASETSOURCE_FILESOURCE = _descriptor.Descriptor( - name='FileSource', - full_name='feast.serving.DatasetSource.FileSource', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='file_uris', full_name='feast.serving.DatasetSource.FileSource.file_uris', index=0, - number=1, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='data_format', full_name='feast.serving.DatasetSource.FileSource.data_format', index=1, - number=2, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1641, - serialized_end=1720, -) - -_DATASETSOURCE = _descriptor.Descriptor( - name='DatasetSource', - full_name='feast.serving.DatasetSource', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='file_source', full_name='feast.serving.DatasetSource.file_source', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[_DATASETSOURCE_FILESOURCE, ], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - _descriptor.OneofDescriptor( - name='dataset_source', full_name='feast.serving.DatasetSource.dataset_source', - index=0, containing_type=None, fields=[]), - ], - serialized_start=1560, - serialized_end=1738, -) - -_GETFEASTSERVINGINFORESPONSE.fields_by_name['type'].enum_type = _FEASTSERVINGTYPE -_FEATUREREFERENCE.fields_by_name['max_age'].message_type = google_dot_protobuf_dot_duration__pb2._DURATION -_GETONLINEFEATURESREQUEST_ENTITYROW_FIELDSENTRY.fields_by_name['value'].message_type = feast_dot_types_dot_Value__pb2._VALUE -_GETONLINEFEATURESREQUEST_ENTITYROW_FIELDSENTRY.containing_type = _GETONLINEFEATURESREQUEST_ENTITYROW -_GETONLINEFEATURESREQUEST_ENTITYROW.fields_by_name['entity_timestamp'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP -_GETONLINEFEATURESREQUEST_ENTITYROW.fields_by_name['fields'].message_type = _GETONLINEFEATURESREQUEST_ENTITYROW_FIELDSENTRY -_GETONLINEFEATURESREQUEST_ENTITYROW.containing_type = _GETONLINEFEATURESREQUEST -_GETONLINEFEATURESREQUEST.fields_by_name['features'].message_type = _FEATUREREFERENCE -_GETONLINEFEATURESREQUEST.fields_by_name['entity_rows'].message_type = _GETONLINEFEATURESREQUEST_ENTITYROW -_GETBATCHFEATURESREQUEST.fields_by_name['features'].message_type = _FEATUREREFERENCE -_GETBATCHFEATURESREQUEST.fields_by_name['dataset_source'].message_type = _DATASETSOURCE -_GETONLINEFEATURESRESPONSE_FIELDVALUES_FIELDSENTRY.fields_by_name['value'].message_type = feast_dot_types_dot_Value__pb2._VALUE -_GETONLINEFEATURESRESPONSE_FIELDVALUES_FIELDSENTRY.containing_type = _GETONLINEFEATURESRESPONSE_FIELDVALUES -_GETONLINEFEATURESRESPONSE_FIELDVALUES.fields_by_name['fields'].message_type = _GETONLINEFEATURESRESPONSE_FIELDVALUES_FIELDSENTRY -_GETONLINEFEATURESRESPONSE_FIELDVALUES.containing_type = _GETONLINEFEATURESRESPONSE -_GETONLINEFEATURESRESPONSE.fields_by_name['field_values'].message_type = _GETONLINEFEATURESRESPONSE_FIELDVALUES -_GETBATCHFEATURESRESPONSE.fields_by_name['job'].message_type = _JOB -_GETJOBREQUEST.fields_by_name['job'].message_type = _JOB -_GETJOBRESPONSE.fields_by_name['job'].message_type = _JOB -_JOB.fields_by_name['type'].enum_type = _JOBTYPE -_JOB.fields_by_name['status'].enum_type = _JOBSTATUS -_JOB.fields_by_name['data_format'].enum_type = _DATAFORMAT -_DATASETSOURCE_FILESOURCE.fields_by_name['data_format'].enum_type = _DATAFORMAT -_DATASETSOURCE_FILESOURCE.containing_type = _DATASETSOURCE -_DATASETSOURCE.fields_by_name['file_source'].message_type = _DATASETSOURCE_FILESOURCE -_DATASETSOURCE.oneofs_by_name['dataset_source'].fields.append( - _DATASETSOURCE.fields_by_name['file_source']) -_DATASETSOURCE.fields_by_name['file_source'].containing_oneof = _DATASETSOURCE.oneofs_by_name['dataset_source'] -DESCRIPTOR.message_types_by_name['GetFeastServingInfoRequest'] = _GETFEASTSERVINGINFOREQUEST -DESCRIPTOR.message_types_by_name['GetFeastServingInfoResponse'] = _GETFEASTSERVINGINFORESPONSE -DESCRIPTOR.message_types_by_name['FeatureReference'] = _FEATUREREFERENCE -DESCRIPTOR.message_types_by_name['GetOnlineFeaturesRequest'] = _GETONLINEFEATURESREQUEST -DESCRIPTOR.message_types_by_name['GetBatchFeaturesRequest'] = _GETBATCHFEATURESREQUEST -DESCRIPTOR.message_types_by_name['GetOnlineFeaturesResponse'] = _GETONLINEFEATURESRESPONSE -DESCRIPTOR.message_types_by_name['GetBatchFeaturesResponse'] = _GETBATCHFEATURESRESPONSE -DESCRIPTOR.message_types_by_name['GetJobRequest'] = _GETJOBREQUEST -DESCRIPTOR.message_types_by_name['GetJobResponse'] = _GETJOBRESPONSE -DESCRIPTOR.message_types_by_name['Job'] = _JOB -DESCRIPTOR.message_types_by_name['DatasetSource'] = _DATASETSOURCE -DESCRIPTOR.enum_types_by_name['FeastServingType'] = _FEASTSERVINGTYPE -DESCRIPTOR.enum_types_by_name['JobType'] = _JOBTYPE -DESCRIPTOR.enum_types_by_name['JobStatus'] = _JOBSTATUS -DESCRIPTOR.enum_types_by_name['DataFormat'] = _DATAFORMAT -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -GetFeastServingInfoRequest = _reflection.GeneratedProtocolMessageType('GetFeastServingInfoRequest', (_message.Message,), { - 'DESCRIPTOR' : _GETFEASTSERVINGINFOREQUEST, - '__module__' : 'feast.serving.ServingService_pb2' - # @@protoc_insertion_point(class_scope:feast.serving.GetFeastServingInfoRequest) - }) -_sym_db.RegisterMessage(GetFeastServingInfoRequest) - -GetFeastServingInfoResponse = _reflection.GeneratedProtocolMessageType('GetFeastServingInfoResponse', (_message.Message,), { - 'DESCRIPTOR' : _GETFEASTSERVINGINFORESPONSE, - '__module__' : 'feast.serving.ServingService_pb2' - # @@protoc_insertion_point(class_scope:feast.serving.GetFeastServingInfoResponse) - }) -_sym_db.RegisterMessage(GetFeastServingInfoResponse) - -FeatureReference = _reflection.GeneratedProtocolMessageType('FeatureReference', (_message.Message,), { - 'DESCRIPTOR' : _FEATUREREFERENCE, - '__module__' : 'feast.serving.ServingService_pb2' - # @@protoc_insertion_point(class_scope:feast.serving.FeatureReference) - }) -_sym_db.RegisterMessage(FeatureReference) - -GetOnlineFeaturesRequest = _reflection.GeneratedProtocolMessageType('GetOnlineFeaturesRequest', (_message.Message,), { - - 'EntityRow' : _reflection.GeneratedProtocolMessageType('EntityRow', (_message.Message,), { - - 'FieldsEntry' : _reflection.GeneratedProtocolMessageType('FieldsEntry', (_message.Message,), { - 'DESCRIPTOR' : _GETONLINEFEATURESREQUEST_ENTITYROW_FIELDSENTRY, - '__module__' : 'feast.serving.ServingService_pb2' - # @@protoc_insertion_point(class_scope:feast.serving.GetOnlineFeaturesRequest.EntityRow.FieldsEntry) - }) - , - 'DESCRIPTOR' : _GETONLINEFEATURESREQUEST_ENTITYROW, - '__module__' : 'feast.serving.ServingService_pb2' - # @@protoc_insertion_point(class_scope:feast.serving.GetOnlineFeaturesRequest.EntityRow) - }) - , - 'DESCRIPTOR' : _GETONLINEFEATURESREQUEST, - '__module__' : 'feast.serving.ServingService_pb2' - # @@protoc_insertion_point(class_scope:feast.serving.GetOnlineFeaturesRequest) - }) -_sym_db.RegisterMessage(GetOnlineFeaturesRequest) -_sym_db.RegisterMessage(GetOnlineFeaturesRequest.EntityRow) -_sym_db.RegisterMessage(GetOnlineFeaturesRequest.EntityRow.FieldsEntry) - -GetBatchFeaturesRequest = _reflection.GeneratedProtocolMessageType('GetBatchFeaturesRequest', (_message.Message,), { - 'DESCRIPTOR' : _GETBATCHFEATURESREQUEST, - '__module__' : 'feast.serving.ServingService_pb2' - # @@protoc_insertion_point(class_scope:feast.serving.GetBatchFeaturesRequest) - }) -_sym_db.RegisterMessage(GetBatchFeaturesRequest) - -GetOnlineFeaturesResponse = _reflection.GeneratedProtocolMessageType('GetOnlineFeaturesResponse', (_message.Message,), { - - 'FieldValues' : _reflection.GeneratedProtocolMessageType('FieldValues', (_message.Message,), { - - 'FieldsEntry' : _reflection.GeneratedProtocolMessageType('FieldsEntry', (_message.Message,), { - 'DESCRIPTOR' : _GETONLINEFEATURESRESPONSE_FIELDVALUES_FIELDSENTRY, - '__module__' : 'feast.serving.ServingService_pb2' - # @@protoc_insertion_point(class_scope:feast.serving.GetOnlineFeaturesResponse.FieldValues.FieldsEntry) - }) - , - 'DESCRIPTOR' : _GETONLINEFEATURESRESPONSE_FIELDVALUES, - '__module__' : 'feast.serving.ServingService_pb2' - # @@protoc_insertion_point(class_scope:feast.serving.GetOnlineFeaturesResponse.FieldValues) - }) - , - 'DESCRIPTOR' : _GETONLINEFEATURESRESPONSE, - '__module__' : 'feast.serving.ServingService_pb2' - # @@protoc_insertion_point(class_scope:feast.serving.GetOnlineFeaturesResponse) - }) -_sym_db.RegisterMessage(GetOnlineFeaturesResponse) -_sym_db.RegisterMessage(GetOnlineFeaturesResponse.FieldValues) -_sym_db.RegisterMessage(GetOnlineFeaturesResponse.FieldValues.FieldsEntry) - -GetBatchFeaturesResponse = _reflection.GeneratedProtocolMessageType('GetBatchFeaturesResponse', (_message.Message,), { - 'DESCRIPTOR' : _GETBATCHFEATURESRESPONSE, - '__module__' : 'feast.serving.ServingService_pb2' - # @@protoc_insertion_point(class_scope:feast.serving.GetBatchFeaturesResponse) - }) -_sym_db.RegisterMessage(GetBatchFeaturesResponse) - -GetJobRequest = _reflection.GeneratedProtocolMessageType('GetJobRequest', (_message.Message,), { - 'DESCRIPTOR' : _GETJOBREQUEST, - '__module__' : 'feast.serving.ServingService_pb2' - # @@protoc_insertion_point(class_scope:feast.serving.GetJobRequest) - }) -_sym_db.RegisterMessage(GetJobRequest) - -GetJobResponse = _reflection.GeneratedProtocolMessageType('GetJobResponse', (_message.Message,), { - 'DESCRIPTOR' : _GETJOBRESPONSE, - '__module__' : 'feast.serving.ServingService_pb2' - # @@protoc_insertion_point(class_scope:feast.serving.GetJobResponse) - }) -_sym_db.RegisterMessage(GetJobResponse) - -Job = _reflection.GeneratedProtocolMessageType('Job', (_message.Message,), { - 'DESCRIPTOR' : _JOB, - '__module__' : 'feast.serving.ServingService_pb2' - # @@protoc_insertion_point(class_scope:feast.serving.Job) - }) -_sym_db.RegisterMessage(Job) - -DatasetSource = _reflection.GeneratedProtocolMessageType('DatasetSource', (_message.Message,), { - - 'FileSource' : _reflection.GeneratedProtocolMessageType('FileSource', (_message.Message,), { - 'DESCRIPTOR' : _DATASETSOURCE_FILESOURCE, - '__module__' : 'feast.serving.ServingService_pb2' - # @@protoc_insertion_point(class_scope:feast.serving.DatasetSource.FileSource) - }) - , - 'DESCRIPTOR' : _DATASETSOURCE, - '__module__' : 'feast.serving.ServingService_pb2' - # @@protoc_insertion_point(class_scope:feast.serving.DatasetSource) - }) -_sym_db.RegisterMessage(DatasetSource) -_sym_db.RegisterMessage(DatasetSource.FileSource) - - -DESCRIPTOR._options = None -_GETONLINEFEATURESREQUEST_ENTITYROW_FIELDSENTRY._options = None -_GETONLINEFEATURESRESPONSE_FIELDVALUES_FIELDSENTRY._options = None - -_SERVINGSERVICE = _descriptor.ServiceDescriptor( - name='ServingService', - full_name='feast.serving.ServingService', - file=DESCRIPTOR, - index=0, - serialized_options=None, - serialized_start=2077, - serialized_end=2479, - methods=[ - _descriptor.MethodDescriptor( - name='GetFeastServingInfo', - full_name='feast.serving.ServingService.GetFeastServingInfo', - index=0, - containing_service=None, - input_type=_GETFEASTSERVINGINFOREQUEST, - output_type=_GETFEASTSERVINGINFORESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='GetOnlineFeatures', - full_name='feast.serving.ServingService.GetOnlineFeatures', - index=1, - containing_service=None, - input_type=_GETONLINEFEATURESREQUEST, - output_type=_GETONLINEFEATURESRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='GetBatchFeatures', - full_name='feast.serving.ServingService.GetBatchFeatures', - index=2, - containing_service=None, - input_type=_GETBATCHFEATURESREQUEST, - output_type=_GETBATCHFEATURESRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='GetJob', - full_name='feast.serving.ServingService.GetJob', - index=3, - containing_service=None, - input_type=_GETJOBREQUEST, - output_type=_GETJOBRESPONSE, - serialized_options=None, - ), -]) -_sym_db.RegisterServiceDescriptor(_SERVINGSERVICE) - -DESCRIPTOR.services_by_name['ServingService'] = _SERVINGSERVICE - -# @@protoc_insertion_point(module_scope) diff --git a/sdk/python/feast/serving/ServingService_pb2.pyi b/sdk/python/feast/serving/ServingService_pb2.pyi deleted file mode 100644 index e10245d6c7a..00000000000 --- a/sdk/python/feast/serving/ServingService_pb2.pyi +++ /dev/null @@ -1,465 +0,0 @@ -# @generated by generate_proto_mypy_stubs.py. Do not edit! -import sys -from feast.types.Value_pb2 import ( - Value as feast___types___Value_pb2___Value, -) - -from google.protobuf.descriptor import ( - Descriptor as google___protobuf___descriptor___Descriptor, - EnumDescriptor as google___protobuf___descriptor___EnumDescriptor, -) - -from google.protobuf.duration_pb2 import ( - Duration as google___protobuf___duration_pb2___Duration, -) - -from google.protobuf.internal.containers import ( - RepeatedCompositeFieldContainer as google___protobuf___internal___containers___RepeatedCompositeFieldContainer, - RepeatedScalarFieldContainer as google___protobuf___internal___containers___RepeatedScalarFieldContainer, -) - -from google.protobuf.message import ( - Message as google___protobuf___message___Message, -) - -from google.protobuf.timestamp_pb2 import ( - Timestamp as google___protobuf___timestamp_pb2___Timestamp, -) - -from typing import ( - Iterable as typing___Iterable, - List as typing___List, - Mapping as typing___Mapping, - MutableMapping as typing___MutableMapping, - Optional as typing___Optional, - Text as typing___Text, - Tuple as typing___Tuple, - cast as typing___cast, -) - -from typing_extensions import ( - Literal as typing_extensions___Literal, -) - - -class FeastServingType(int): - DESCRIPTOR: google___protobuf___descriptor___EnumDescriptor = ... - @classmethod - def Name(cls, number: int) -> str: ... - @classmethod - def Value(cls, name: str) -> FeastServingType: ... - @classmethod - def keys(cls) -> typing___List[str]: ... - @classmethod - def values(cls) -> typing___List[FeastServingType]: ... - @classmethod - def items(cls) -> typing___List[typing___Tuple[str, FeastServingType]]: ... - FEAST_SERVING_TYPE_INVALID = typing___cast(FeastServingType, 0) - FEAST_SERVING_TYPE_ONLINE = typing___cast(FeastServingType, 1) - FEAST_SERVING_TYPE_BATCH = typing___cast(FeastServingType, 2) -FEAST_SERVING_TYPE_INVALID = typing___cast(FeastServingType, 0) -FEAST_SERVING_TYPE_ONLINE = typing___cast(FeastServingType, 1) -FEAST_SERVING_TYPE_BATCH = typing___cast(FeastServingType, 2) - -class JobType(int): - DESCRIPTOR: google___protobuf___descriptor___EnumDescriptor = ... - @classmethod - def Name(cls, number: int) -> str: ... - @classmethod - def Value(cls, name: str) -> JobType: ... - @classmethod - def keys(cls) -> typing___List[str]: ... - @classmethod - def values(cls) -> typing___List[JobType]: ... - @classmethod - def items(cls) -> typing___List[typing___Tuple[str, JobType]]: ... - JOB_TYPE_INVALID = typing___cast(JobType, 0) - JOB_TYPE_DOWNLOAD = typing___cast(JobType, 1) -JOB_TYPE_INVALID = typing___cast(JobType, 0) -JOB_TYPE_DOWNLOAD = typing___cast(JobType, 1) - -class JobStatus(int): - DESCRIPTOR: google___protobuf___descriptor___EnumDescriptor = ... - @classmethod - def Name(cls, number: int) -> str: ... - @classmethod - def Value(cls, name: str) -> JobStatus: ... - @classmethod - def keys(cls) -> typing___List[str]: ... - @classmethod - def values(cls) -> typing___List[JobStatus]: ... - @classmethod - def items(cls) -> typing___List[typing___Tuple[str, JobStatus]]: ... - JOB_STATUS_INVALID = typing___cast(JobStatus, 0) - JOB_STATUS_PENDING = typing___cast(JobStatus, 1) - JOB_STATUS_RUNNING = typing___cast(JobStatus, 2) - JOB_STATUS_DONE = typing___cast(JobStatus, 3) -JOB_STATUS_INVALID = typing___cast(JobStatus, 0) -JOB_STATUS_PENDING = typing___cast(JobStatus, 1) -JOB_STATUS_RUNNING = typing___cast(JobStatus, 2) -JOB_STATUS_DONE = typing___cast(JobStatus, 3) - -class DataFormat(int): - DESCRIPTOR: google___protobuf___descriptor___EnumDescriptor = ... - @classmethod - def Name(cls, number: int) -> str: ... - @classmethod - def Value(cls, name: str) -> DataFormat: ... - @classmethod - def keys(cls) -> typing___List[str]: ... - @classmethod - def values(cls) -> typing___List[DataFormat]: ... - @classmethod - def items(cls) -> typing___List[typing___Tuple[str, DataFormat]]: ... - DATA_FORMAT_INVALID = typing___cast(DataFormat, 0) - DATA_FORMAT_AVRO = typing___cast(DataFormat, 1) -DATA_FORMAT_INVALID = typing___cast(DataFormat, 0) -DATA_FORMAT_AVRO = typing___cast(DataFormat, 1) - -class GetFeastServingInfoRequest(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - - def __init__(self, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> GetFeastServingInfoRequest: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - -class GetFeastServingInfoResponse(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - version = ... # type: typing___Text - type = ... # type: FeastServingType - job_staging_location = ... # type: typing___Text - - def __init__(self, - *, - version : typing___Optional[typing___Text] = None, - type : typing___Optional[FeastServingType] = None, - job_staging_location : typing___Optional[typing___Text] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> GetFeastServingInfoResponse: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"job_staging_location",u"type",u"version"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"job_staging_location",b"job_staging_location",u"type",b"type",u"version",b"version"]) -> None: ... - -class FeatureReference(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - project = ... # type: typing___Text - name = ... # type: typing___Text - version = ... # type: int - - @property - def max_age(self) -> google___protobuf___duration_pb2___Duration: ... - - def __init__(self, - *, - project : typing___Optional[typing___Text] = None, - name : typing___Optional[typing___Text] = None, - version : typing___Optional[int] = None, - max_age : typing___Optional[google___protobuf___duration_pb2___Duration] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> FeatureReference: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def HasField(self, field_name: typing_extensions___Literal[u"max_age"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"max_age",u"name",u"project",u"version"]) -> None: ... - else: - def HasField(self, field_name: typing_extensions___Literal[u"max_age",b"max_age"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"max_age",b"max_age",u"name",b"name",u"project",b"project",u"version",b"version"]) -> None: ... - -class GetOnlineFeaturesRequest(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - class EntityRow(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - class FieldsEntry(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - key = ... # type: typing___Text - - @property - def value(self) -> feast___types___Value_pb2___Value: ... - - def __init__(self, - *, - key : typing___Optional[typing___Text] = None, - value : typing___Optional[feast___types___Value_pb2___Value] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> GetOnlineFeaturesRequest.EntityRow.FieldsEntry: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def HasField(self, field_name: typing_extensions___Literal[u"value"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"key",u"value"]) -> None: ... - else: - def HasField(self, field_name: typing_extensions___Literal[u"value",b"value"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"key",b"key",u"value",b"value"]) -> None: ... - - - @property - def entity_timestamp(self) -> google___protobuf___timestamp_pb2___Timestamp: ... - - @property - def fields(self) -> typing___MutableMapping[typing___Text, feast___types___Value_pb2___Value]: ... - - def __init__(self, - *, - entity_timestamp : typing___Optional[google___protobuf___timestamp_pb2___Timestamp] = None, - fields : typing___Optional[typing___Mapping[typing___Text, feast___types___Value_pb2___Value]] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> GetOnlineFeaturesRequest.EntityRow: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def HasField(self, field_name: typing_extensions___Literal[u"entity_timestamp"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"entity_timestamp",u"fields"]) -> None: ... - else: - def HasField(self, field_name: typing_extensions___Literal[u"entity_timestamp",b"entity_timestamp"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"entity_timestamp",b"entity_timestamp",u"fields",b"fields"]) -> None: ... - - omit_entities_in_response = ... # type: bool - - @property - def features(self) -> google___protobuf___internal___containers___RepeatedCompositeFieldContainer[FeatureReference]: ... - - @property - def entity_rows(self) -> google___protobuf___internal___containers___RepeatedCompositeFieldContainer[GetOnlineFeaturesRequest.EntityRow]: ... - - def __init__(self, - *, - features : typing___Optional[typing___Iterable[FeatureReference]] = None, - entity_rows : typing___Optional[typing___Iterable[GetOnlineFeaturesRequest.EntityRow]] = None, - omit_entities_in_response : typing___Optional[bool] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> GetOnlineFeaturesRequest: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"entity_rows",u"features",u"omit_entities_in_response"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"entity_rows",b"entity_rows",u"features",b"features",u"omit_entities_in_response",b"omit_entities_in_response"]) -> None: ... - -class GetBatchFeaturesRequest(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - - @property - def features(self) -> google___protobuf___internal___containers___RepeatedCompositeFieldContainer[FeatureReference]: ... - - @property - def dataset_source(self) -> DatasetSource: ... - - def __init__(self, - *, - features : typing___Optional[typing___Iterable[FeatureReference]] = None, - dataset_source : typing___Optional[DatasetSource] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> GetBatchFeaturesRequest: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def HasField(self, field_name: typing_extensions___Literal[u"dataset_source"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"dataset_source",u"features"]) -> None: ... - else: - def HasField(self, field_name: typing_extensions___Literal[u"dataset_source",b"dataset_source"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"dataset_source",b"dataset_source",u"features",b"features"]) -> None: ... - -class GetOnlineFeaturesResponse(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - class FieldValues(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - class FieldsEntry(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - key = ... # type: typing___Text - - @property - def value(self) -> feast___types___Value_pb2___Value: ... - - def __init__(self, - *, - key : typing___Optional[typing___Text] = None, - value : typing___Optional[feast___types___Value_pb2___Value] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> GetOnlineFeaturesResponse.FieldValues.FieldsEntry: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def HasField(self, field_name: typing_extensions___Literal[u"value"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"key",u"value"]) -> None: ... - else: - def HasField(self, field_name: typing_extensions___Literal[u"value",b"value"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"key",b"key",u"value",b"value"]) -> None: ... - - - @property - def fields(self) -> typing___MutableMapping[typing___Text, feast___types___Value_pb2___Value]: ... - - def __init__(self, - *, - fields : typing___Optional[typing___Mapping[typing___Text, feast___types___Value_pb2___Value]] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> GetOnlineFeaturesResponse.FieldValues: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"fields"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"fields",b"fields"]) -> None: ... - - - @property - def field_values(self) -> google___protobuf___internal___containers___RepeatedCompositeFieldContainer[GetOnlineFeaturesResponse.FieldValues]: ... - - def __init__(self, - *, - field_values : typing___Optional[typing___Iterable[GetOnlineFeaturesResponse.FieldValues]] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> GetOnlineFeaturesResponse: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"field_values"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"field_values",b"field_values"]) -> None: ... - -class GetBatchFeaturesResponse(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - - @property - def job(self) -> Job: ... - - def __init__(self, - *, - job : typing___Optional[Job] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> GetBatchFeaturesResponse: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def HasField(self, field_name: typing_extensions___Literal[u"job"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"job"]) -> None: ... - else: - def HasField(self, field_name: typing_extensions___Literal[u"job",b"job"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"job",b"job"]) -> None: ... - -class GetJobRequest(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - - @property - def job(self) -> Job: ... - - def __init__(self, - *, - job : typing___Optional[Job] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> GetJobRequest: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def HasField(self, field_name: typing_extensions___Literal[u"job"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"job"]) -> None: ... - else: - def HasField(self, field_name: typing_extensions___Literal[u"job",b"job"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"job",b"job"]) -> None: ... - -class GetJobResponse(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - - @property - def job(self) -> Job: ... - - def __init__(self, - *, - job : typing___Optional[Job] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> GetJobResponse: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def HasField(self, field_name: typing_extensions___Literal[u"job"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"job"]) -> None: ... - else: - def HasField(self, field_name: typing_extensions___Literal[u"job",b"job"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"job",b"job"]) -> None: ... - -class Job(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - id = ... # type: typing___Text - type = ... # type: JobType - status = ... # type: JobStatus - error = ... # type: typing___Text - file_uris = ... # type: google___protobuf___internal___containers___RepeatedScalarFieldContainer[typing___Text] - data_format = ... # type: DataFormat - - def __init__(self, - *, - id : typing___Optional[typing___Text] = None, - type : typing___Optional[JobType] = None, - status : typing___Optional[JobStatus] = None, - error : typing___Optional[typing___Text] = None, - file_uris : typing___Optional[typing___Iterable[typing___Text]] = None, - data_format : typing___Optional[DataFormat] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> Job: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"data_format",u"error",u"file_uris",u"id",u"status",u"type"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"data_format",b"data_format",u"error",b"error",u"file_uris",b"file_uris",u"id",b"id",u"status",b"status",u"type",b"type"]) -> None: ... - -class DatasetSource(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - class FileSource(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - file_uris = ... # type: google___protobuf___internal___containers___RepeatedScalarFieldContainer[typing___Text] - data_format = ... # type: DataFormat - - def __init__(self, - *, - file_uris : typing___Optional[typing___Iterable[typing___Text]] = None, - data_format : typing___Optional[DataFormat] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> DatasetSource.FileSource: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"data_format",u"file_uris"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"data_format",b"data_format",u"file_uris",b"file_uris"]) -> None: ... - - - @property - def file_source(self) -> DatasetSource.FileSource: ... - - def __init__(self, - *, - file_source : typing___Optional[DatasetSource.FileSource] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> DatasetSource: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def HasField(self, field_name: typing_extensions___Literal[u"dataset_source",u"file_source"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"dataset_source",u"file_source"]) -> None: ... - else: - def HasField(self, field_name: typing_extensions___Literal[u"dataset_source",b"dataset_source",u"file_source",b"file_source"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"dataset_source",b"dataset_source",u"file_source",b"file_source"]) -> None: ... - def WhichOneof(self, oneof_group: typing_extensions___Literal[u"dataset_source",b"dataset_source"]) -> typing_extensions___Literal["file_source"]: ... diff --git a/sdk/python/feast/serving/ServingService_pb2_grpc.py b/sdk/python/feast/serving/ServingService_pb2_grpc.py deleted file mode 100644 index c73f9c744a6..00000000000 --- a/sdk/python/feast/serving/ServingService_pb2_grpc.py +++ /dev/null @@ -1,104 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -import grpc - -from feast.serving import ServingService_pb2 as feast_dot_serving_dot_ServingService__pb2 - - -class ServingServiceStub(object): - # missing associated documentation comment in .proto file - pass - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.GetFeastServingInfo = channel.unary_unary( - '/feast.serving.ServingService/GetFeastServingInfo', - request_serializer=feast_dot_serving_dot_ServingService__pb2.GetFeastServingInfoRequest.SerializeToString, - response_deserializer=feast_dot_serving_dot_ServingService__pb2.GetFeastServingInfoResponse.FromString, - ) - self.GetOnlineFeatures = channel.unary_unary( - '/feast.serving.ServingService/GetOnlineFeatures', - request_serializer=feast_dot_serving_dot_ServingService__pb2.GetOnlineFeaturesRequest.SerializeToString, - response_deserializer=feast_dot_serving_dot_ServingService__pb2.GetOnlineFeaturesResponse.FromString, - ) - self.GetBatchFeatures = channel.unary_unary( - '/feast.serving.ServingService/GetBatchFeatures', - request_serializer=feast_dot_serving_dot_ServingService__pb2.GetBatchFeaturesRequest.SerializeToString, - response_deserializer=feast_dot_serving_dot_ServingService__pb2.GetBatchFeaturesResponse.FromString, - ) - self.GetJob = channel.unary_unary( - '/feast.serving.ServingService/GetJob', - request_serializer=feast_dot_serving_dot_ServingService__pb2.GetJobRequest.SerializeToString, - response_deserializer=feast_dot_serving_dot_ServingService__pb2.GetJobResponse.FromString, - ) - - -class ServingServiceServicer(object): - # missing associated documentation comment in .proto file - pass - - def GetFeastServingInfo(self, request, context): - """Get information about this Feast serving. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetOnlineFeatures(self, request, context): - """Get online features synchronously. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetBatchFeatures(self, request, context): - """Get batch features asynchronously. - - The client should check the status of the returned job periodically by - calling ReloadJob to determine if the job has completed successfully - or with an error. If the job completes successfully i.e. - status = JOB_STATUS_DONE with no error, then the client can check - the file_uris for the location to download feature values data. - The client is assumed to have access to these file URIs. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetJob(self, request, context): - """Get the latest job status for batch feature retrieval. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_ServingServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'GetFeastServingInfo': grpc.unary_unary_rpc_method_handler( - servicer.GetFeastServingInfo, - request_deserializer=feast_dot_serving_dot_ServingService__pb2.GetFeastServingInfoRequest.FromString, - response_serializer=feast_dot_serving_dot_ServingService__pb2.GetFeastServingInfoResponse.SerializeToString, - ), - 'GetOnlineFeatures': grpc.unary_unary_rpc_method_handler( - servicer.GetOnlineFeatures, - request_deserializer=feast_dot_serving_dot_ServingService__pb2.GetOnlineFeaturesRequest.FromString, - response_serializer=feast_dot_serving_dot_ServingService__pb2.GetOnlineFeaturesResponse.SerializeToString, - ), - 'GetBatchFeatures': grpc.unary_unary_rpc_method_handler( - servicer.GetBatchFeatures, - request_deserializer=feast_dot_serving_dot_ServingService__pb2.GetBatchFeaturesRequest.FromString, - response_serializer=feast_dot_serving_dot_ServingService__pb2.GetBatchFeaturesResponse.SerializeToString, - ), - 'GetJob': grpc.unary_unary_rpc_method_handler( - servicer.GetJob, - request_deserializer=feast_dot_serving_dot_ServingService__pb2.GetJobRequest.FromString, - response_serializer=feast_dot_serving_dot_ServingService__pb2.GetJobResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'feast.serving.ServingService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) diff --git a/sdk/python/feast/source.py b/sdk/python/feast/source.py index 10c21210780..8e388376b3c 100644 --- a/sdk/python/feast/source.py +++ b/sdk/python/feast/source.py @@ -11,11 +11,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. -from feast.core.Source_pb2 import ( - Source as SourceProto, - KafkaSourceConfig as KafkaSourceConfigProto, - SourceType as SourceTypeProto, -) +from feast.core.Source_pb2 import KafkaSourceConfig as KafkaSourceConfigProto +from feast.core.Source_pb2 import Source as SourceProto +from feast.core.Source_pb2 import SourceType as SourceTypeProto class Source: diff --git a/sdk/python/feast/storage/Redis_pb2.py b/sdk/python/feast/storage/Redis_pb2.py deleted file mode 100644 index 49b0b793781..00000000000 --- a/sdk/python/feast/storage/Redis_pb2.py +++ /dev/null @@ -1,81 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: feast/storage/Redis.proto - -import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from feast.types import Field_pb2 as feast_dot_types_dot_Field__pb2 - - -DESCRIPTOR = _descriptor.FileDescriptor( - name='feast/storage/Redis.proto', - package='feast.storage', - syntax='proto3', - serialized_options=_b('\n\rfeast.storageB\nRedisProtoZ2github.com/gojek/feast/sdk/go/protos/feast/storage'), - serialized_pb=_b('\n\x19\x66\x65\x61st/storage/Redis.proto\x12\rfeast.storage\x1a\x17\x66\x65\x61st/types/Field.proto\"E\n\x08RedisKey\x12\x13\n\x0b\x66\x65\x61ture_set\x18\x02 \x01(\t\x12$\n\x08\x65ntities\x18\x03 \x03(\x0b\x32\x12.feast.types.FieldBO\n\rfeast.storageB\nRedisProtoZ2github.com/gojek/feast/sdk/go/protos/feast/storageb\x06proto3') - , - dependencies=[feast_dot_types_dot_Field__pb2.DESCRIPTOR,]) - - - - -_REDISKEY = _descriptor.Descriptor( - name='RedisKey', - full_name='feast.storage.RedisKey', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='feature_set', full_name='feast.storage.RedisKey.feature_set', index=0, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='entities', full_name='feast.storage.RedisKey.entities', index=1, - number=3, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=69, - serialized_end=138, -) - -_REDISKEY.fields_by_name['entities'].message_type = feast_dot_types_dot_Field__pb2._FIELD -DESCRIPTOR.message_types_by_name['RedisKey'] = _REDISKEY -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -RedisKey = _reflection.GeneratedProtocolMessageType('RedisKey', (_message.Message,), { - 'DESCRIPTOR' : _REDISKEY, - '__module__' : 'feast.storage.Redis_pb2' - # @@protoc_insertion_point(class_scope:feast.storage.RedisKey) - }) -_sym_db.RegisterMessage(RedisKey) - - -DESCRIPTOR._options = None -# @@protoc_insertion_point(module_scope) diff --git a/sdk/python/feast/storage/Redis_pb2.pyi b/sdk/python/feast/storage/Redis_pb2.pyi deleted file mode 100644 index 717aae79db2..00000000000 --- a/sdk/python/feast/storage/Redis_pb2.pyi +++ /dev/null @@ -1,49 +0,0 @@ -# @generated by generate_proto_mypy_stubs.py. Do not edit! -import sys -from feast.types.Field_pb2 import ( - Field as feast___types___Field_pb2___Field, -) - -from google.protobuf.descriptor import ( - Descriptor as google___protobuf___descriptor___Descriptor, -) - -from google.protobuf.internal.containers import ( - RepeatedCompositeFieldContainer as google___protobuf___internal___containers___RepeatedCompositeFieldContainer, -) - -from google.protobuf.message import ( - Message as google___protobuf___message___Message, -) - -from typing import ( - Iterable as typing___Iterable, - Optional as typing___Optional, - Text as typing___Text, -) - -from typing_extensions import ( - Literal as typing_extensions___Literal, -) - - -class RedisKey(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - feature_set = ... # type: typing___Text - - @property - def entities(self) -> google___protobuf___internal___containers___RepeatedCompositeFieldContainer[feast___types___Field_pb2___Field]: ... - - def __init__(self, - *, - feature_set : typing___Optional[typing___Text] = None, - entities : typing___Optional[typing___Iterable[feast___types___Field_pb2___Field]] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> RedisKey: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"entities",u"feature_set"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"entities",b"entities",u"feature_set",b"feature_set"]) -> None: ... diff --git a/sdk/python/feast/test_feature.py b/sdk/python/feast/test_feature.py new file mode 100644 index 00000000000..a4f0ff58ff2 --- /dev/null +++ b/sdk/python/feast/test_feature.py @@ -0,0 +1,23 @@ +# Copyright 2019 The Feast Authors +# +# 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. + +from feast.feature import FeatureRef + + +class TestFeatureRef: + def test_str_ref(self): + original_ref = FeatureRef(project="test", name="test") + ref_str = repr(original_ref) + parsed_ref = FeatureRef.from_str(ref_str) + assert original_ref == parsed_ref diff --git a/sdk/python/feast/type_map.py b/sdk/python/feast/type_map.py index af019c3fdbd..85def25fcb9 100644 --- a/sdk/python/feast/type_map.py +++ b/sdk/python/feast/type_map.py @@ -18,22 +18,24 @@ import numpy as np import pandas as pd import pyarrow as pa +from google.protobuf.timestamp_pb2 import Timestamp +from pyarrow.lib import TimestampType + from feast.constants import DATETIME_COLUMN -from feast.types import FeatureRow_pb2 as FeatureRowProto, Field_pb2 as FieldProto +from feast.types import FeatureRow_pb2 as FeatureRowProto +from feast.types import Field_pb2 as FieldProto from feast.types.Value_pb2 import ( - Value as ProtoValue, - ValueType as ProtoValueType, - Int64List, - Int32List, BoolList, BytesList, DoubleList, - StringList, FloatList, + Int32List, + Int64List, + StringList, ) +from feast.types.Value_pb2 import Value as ProtoValue +from feast.types.Value_pb2 import ValueType as ProtoValueType from feast.value_type import ValueType -from google.protobuf.timestamp_pb2 import Timestamp -from pyarrow.lib import TimestampType def python_type_to_feast_value_type( @@ -145,7 +147,7 @@ def convert_series_to_proto_values(row: pd.Series): event_timestamp=_pd_datetime_to_timestamp_proto( dataframe[DATETIME_COLUMN].dtype, row[DATETIME_COLUMN] ), - feature_set=feature_set.name + ":" + str(feature_set.version), + feature_set=feature_set.project + "/" + feature_set.name, ) for field_name, field in feature_set.fields.items(): @@ -183,11 +185,7 @@ def convert_dict_to_proto_values( event_timestamp=_pd_datetime_to_timestamp_proto( df_datetime_dtype, row[DATETIME_COLUMN] ), - feature_set=feature_set.project - + "/" - + feature_set.name - + ":" - + str(feature_set.version), + feature_set=f"{feature_set.project}/{feature_set.name}", ) for field_name, field in feature_set.fields.items(): diff --git a/sdk/python/feast/types/FeatureRowExtended_pb2.py b/sdk/python/feast/types/FeatureRowExtended_pb2.py deleted file mode 100644 index e7372958168..00000000000 --- a/sdk/python/feast/types/FeatureRowExtended_pb2.py +++ /dev/null @@ -1,198 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: feast/types/FeatureRowExtended.proto - -import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -from feast.types import FeatureRow_pb2 as feast_dot_types_dot_FeatureRow__pb2 - - -DESCRIPTOR = _descriptor.FileDescriptor( - name='feast/types/FeatureRowExtended.proto', - package='feast.types', - syntax='proto3', - serialized_options=_b('\n\013feast.typesB\027FeatureRowExtendedProtoZ0github.com/gojek/feast/sdk/go/protos/feast/types'), - serialized_pb=_b('\n$feast/types/FeatureRowExtended.proto\x12\x0b\x66\x65\x61st.types\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1c\x66\x65\x61st/types/FeatureRow.proto\"O\n\x05\x45rror\x12\r\n\x05\x63\x61use\x18\x01 \x01(\t\x12\x11\n\ttransform\x18\x02 \x01(\t\x12\x0f\n\x07message\x18\x03 \x01(\t\x12\x13\n\x0bstack_trace\x18\x04 \x01(\t\">\n\x07\x41ttempt\x12\x10\n\x08\x61ttempts\x18\x01 \x01(\x05\x12!\n\x05\x65rror\x18\x02 \x01(\x0b\x32\x12.feast.types.Error\"\x96\x01\n\x12\x46\x65\x61tureRowExtended\x12$\n\x03row\x18\x01 \x01(\x0b\x32\x17.feast.types.FeatureRow\x12*\n\x0clast_attempt\x18\x02 \x01(\x0b\x32\x14.feast.types.Attempt\x12.\n\nfirst_seen\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampBX\n\x0b\x66\x65\x61st.typesB\x17\x46\x65\x61tureRowExtendedProtoZ0github.com/gojek/feast/sdk/go/protos/feast/typesb\x06proto3') - , - dependencies=[google_dot_protobuf_dot_timestamp__pb2.DESCRIPTOR,feast_dot_types_dot_FeatureRow__pb2.DESCRIPTOR,]) - - - - -_ERROR = _descriptor.Descriptor( - name='Error', - full_name='feast.types.Error', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='cause', full_name='feast.types.Error.cause', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='transform', full_name='feast.types.Error.transform', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='message', full_name='feast.types.Error.message', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='stack_trace', full_name='feast.types.Error.stack_trace', index=3, - number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=116, - serialized_end=195, -) - - -_ATTEMPT = _descriptor.Descriptor( - name='Attempt', - full_name='feast.types.Attempt', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='attempts', full_name='feast.types.Attempt.attempts', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='error', full_name='feast.types.Attempt.error', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=197, - serialized_end=259, -) - - -_FEATUREROWEXTENDED = _descriptor.Descriptor( - name='FeatureRowExtended', - full_name='feast.types.FeatureRowExtended', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='row', full_name='feast.types.FeatureRowExtended.row', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='last_attempt', full_name='feast.types.FeatureRowExtended.last_attempt', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='first_seen', full_name='feast.types.FeatureRowExtended.first_seen', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=262, - serialized_end=412, -) - -_ATTEMPT.fields_by_name['error'].message_type = _ERROR -_FEATUREROWEXTENDED.fields_by_name['row'].message_type = feast_dot_types_dot_FeatureRow__pb2._FEATUREROW -_FEATUREROWEXTENDED.fields_by_name['last_attempt'].message_type = _ATTEMPT -_FEATUREROWEXTENDED.fields_by_name['first_seen'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP -DESCRIPTOR.message_types_by_name['Error'] = _ERROR -DESCRIPTOR.message_types_by_name['Attempt'] = _ATTEMPT -DESCRIPTOR.message_types_by_name['FeatureRowExtended'] = _FEATUREROWEXTENDED -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -Error = _reflection.GeneratedProtocolMessageType('Error', (_message.Message,), { - 'DESCRIPTOR' : _ERROR, - '__module__' : 'feast.types.FeatureRowExtended_pb2' - # @@protoc_insertion_point(class_scope:feast.types.Error) - }) -_sym_db.RegisterMessage(Error) - -Attempt = _reflection.GeneratedProtocolMessageType('Attempt', (_message.Message,), { - 'DESCRIPTOR' : _ATTEMPT, - '__module__' : 'feast.types.FeatureRowExtended_pb2' - # @@protoc_insertion_point(class_scope:feast.types.Attempt) - }) -_sym_db.RegisterMessage(Attempt) - -FeatureRowExtended = _reflection.GeneratedProtocolMessageType('FeatureRowExtended', (_message.Message,), { - 'DESCRIPTOR' : _FEATUREROWEXTENDED, - '__module__' : 'feast.types.FeatureRowExtended_pb2' - # @@protoc_insertion_point(class_scope:feast.types.FeatureRowExtended) - }) -_sym_db.RegisterMessage(FeatureRowExtended) - - -DESCRIPTOR._options = None -# @@protoc_insertion_point(module_scope) diff --git a/sdk/python/feast/types/FeatureRowExtended_pb2.pyi b/sdk/python/feast/types/FeatureRowExtended_pb2.pyi deleted file mode 100644 index 4f3d02c8ee6..00000000000 --- a/sdk/python/feast/types/FeatureRowExtended_pb2.pyi +++ /dev/null @@ -1,102 +0,0 @@ -# @generated by generate_proto_mypy_stubs.py. Do not edit! -import sys -from feast.types.FeatureRow_pb2 import ( - FeatureRow as feast___types___FeatureRow_pb2___FeatureRow, -) - -from google.protobuf.descriptor import ( - Descriptor as google___protobuf___descriptor___Descriptor, -) - -from google.protobuf.message import ( - Message as google___protobuf___message___Message, -) - -from google.protobuf.timestamp_pb2 import ( - Timestamp as google___protobuf___timestamp_pb2___Timestamp, -) - -from typing import ( - Optional as typing___Optional, - Text as typing___Text, -) - -from typing_extensions import ( - Literal as typing_extensions___Literal, -) - - -class Error(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - cause = ... # type: typing___Text - transform = ... # type: typing___Text - message = ... # type: typing___Text - stack_trace = ... # type: typing___Text - - def __init__(self, - *, - cause : typing___Optional[typing___Text] = None, - transform : typing___Optional[typing___Text] = None, - message : typing___Optional[typing___Text] = None, - stack_trace : typing___Optional[typing___Text] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> Error: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"cause",u"message",u"stack_trace",u"transform"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"cause",b"cause",u"message",b"message",u"stack_trace",b"stack_trace",u"transform",b"transform"]) -> None: ... - -class Attempt(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - attempts = ... # type: int - - @property - def error(self) -> Error: ... - - def __init__(self, - *, - attempts : typing___Optional[int] = None, - error : typing___Optional[Error] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> Attempt: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def HasField(self, field_name: typing_extensions___Literal[u"error"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"attempts",u"error"]) -> None: ... - else: - def HasField(self, field_name: typing_extensions___Literal[u"error",b"error"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"attempts",b"attempts",u"error",b"error"]) -> None: ... - -class FeatureRowExtended(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - - @property - def row(self) -> feast___types___FeatureRow_pb2___FeatureRow: ... - - @property - def last_attempt(self) -> Attempt: ... - - @property - def first_seen(self) -> google___protobuf___timestamp_pb2___Timestamp: ... - - def __init__(self, - *, - row : typing___Optional[feast___types___FeatureRow_pb2___FeatureRow] = None, - last_attempt : typing___Optional[Attempt] = None, - first_seen : typing___Optional[google___protobuf___timestamp_pb2___Timestamp] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> FeatureRowExtended: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def HasField(self, field_name: typing_extensions___Literal[u"first_seen",u"last_attempt",u"row"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"first_seen",u"last_attempt",u"row"]) -> None: ... - else: - def HasField(self, field_name: typing_extensions___Literal[u"first_seen",b"first_seen",u"last_attempt",b"last_attempt",u"row",b"row"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"first_seen",b"first_seen",u"last_attempt",b"last_attempt",u"row",b"row"]) -> None: ... diff --git a/sdk/python/feast/types/FeatureRow_pb2.py b/sdk/python/feast/types/FeatureRow_pb2.py deleted file mode 100644 index 1b6c16910f2..00000000000 --- a/sdk/python/feast/types/FeatureRow_pb2.py +++ /dev/null @@ -1,90 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: feast/types/FeatureRow.proto - -import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -from feast.types import Field_pb2 as feast_dot_types_dot_Field__pb2 - - -DESCRIPTOR = _descriptor.FileDescriptor( - name='feast/types/FeatureRow.proto', - package='feast.types', - syntax='proto3', - serialized_options=_b('\n\013feast.typesB\017FeatureRowProtoZ0github.com/gojek/feast/sdk/go/protos/feast/types'), - serialized_pb=_b('\n\x1c\x66\x65\x61st/types/FeatureRow.proto\x12\x0b\x66\x65\x61st.types\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x17\x66\x65\x61st/types/Field.proto\"z\n\nFeatureRow\x12\"\n\x06\x66ields\x18\x02 \x03(\x0b\x32\x12.feast.types.Field\x12\x33\n\x0f\x65vent_timestamp\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x13\n\x0b\x66\x65\x61ture_set\x18\x06 \x01(\tBP\n\x0b\x66\x65\x61st.typesB\x0f\x46\x65\x61tureRowProtoZ0github.com/gojek/feast/sdk/go/protos/feast/typesb\x06proto3') - , - dependencies=[google_dot_protobuf_dot_timestamp__pb2.DESCRIPTOR,feast_dot_types_dot_Field__pb2.DESCRIPTOR,]) - - - - -_FEATUREROW = _descriptor.Descriptor( - name='FeatureRow', - full_name='feast.types.FeatureRow', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='fields', full_name='feast.types.FeatureRow.fields', index=0, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='event_timestamp', full_name='feast.types.FeatureRow.event_timestamp', index=1, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='feature_set', full_name='feast.types.FeatureRow.feature_set', index=2, - number=6, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=103, - serialized_end=225, -) - -_FEATUREROW.fields_by_name['fields'].message_type = feast_dot_types_dot_Field__pb2._FIELD -_FEATUREROW.fields_by_name['event_timestamp'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP -DESCRIPTOR.message_types_by_name['FeatureRow'] = _FEATUREROW -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -FeatureRow = _reflection.GeneratedProtocolMessageType('FeatureRow', (_message.Message,), { - 'DESCRIPTOR' : _FEATUREROW, - '__module__' : 'feast.types.FeatureRow_pb2' - # @@protoc_insertion_point(class_scope:feast.types.FeatureRow) - }) -_sym_db.RegisterMessage(FeatureRow) - - -DESCRIPTOR._options = None -# @@protoc_insertion_point(module_scope) diff --git a/sdk/python/feast/types/FeatureRow_pb2.pyi b/sdk/python/feast/types/FeatureRow_pb2.pyi deleted file mode 100644 index 9bf745f9130..00000000000 --- a/sdk/python/feast/types/FeatureRow_pb2.pyi +++ /dev/null @@ -1,59 +0,0 @@ -# @generated by generate_proto_mypy_stubs.py. Do not edit! -import sys -from feast.types.Field_pb2 import ( - Field as feast___types___Field_pb2___Field, -) - -from google.protobuf.descriptor import ( - Descriptor as google___protobuf___descriptor___Descriptor, -) - -from google.protobuf.internal.containers import ( - RepeatedCompositeFieldContainer as google___protobuf___internal___containers___RepeatedCompositeFieldContainer, -) - -from google.protobuf.message import ( - Message as google___protobuf___message___Message, -) - -from google.protobuf.timestamp_pb2 import ( - Timestamp as google___protobuf___timestamp_pb2___Timestamp, -) - -from typing import ( - Iterable as typing___Iterable, - Optional as typing___Optional, - Text as typing___Text, -) - -from typing_extensions import ( - Literal as typing_extensions___Literal, -) - - -class FeatureRow(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - feature_set = ... # type: typing___Text - - @property - def fields(self) -> google___protobuf___internal___containers___RepeatedCompositeFieldContainer[feast___types___Field_pb2___Field]: ... - - @property - def event_timestamp(self) -> google___protobuf___timestamp_pb2___Timestamp: ... - - def __init__(self, - *, - fields : typing___Optional[typing___Iterable[feast___types___Field_pb2___Field]] = None, - event_timestamp : typing___Optional[google___protobuf___timestamp_pb2___Timestamp] = None, - feature_set : typing___Optional[typing___Text] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> FeatureRow: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def HasField(self, field_name: typing_extensions___Literal[u"event_timestamp"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"event_timestamp",u"feature_set",u"fields"]) -> None: ... - else: - def HasField(self, field_name: typing_extensions___Literal[u"event_timestamp",b"event_timestamp"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"event_timestamp",b"event_timestamp",u"feature_set",b"feature_set",u"fields",b"fields"]) -> None: ... diff --git a/sdk/python/feast/types/Feature_pb2.py b/sdk/python/feast/types/Feature_pb2.py deleted file mode 100644 index 98d98d88a66..00000000000 --- a/sdk/python/feast/types/Feature_pb2.py +++ /dev/null @@ -1,108 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: feast/types/Feature.proto - -import sys - -_b = sys.version_info[0] < 3 and (lambda x: x) or (lambda x: x.encode("latin1")) -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database - -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from feast.types import Value_pb2 as feast_dot_types_dot_Value__pb2 - - -DESCRIPTOR = _descriptor.FileDescriptor( - name="feast/types/Feature.proto", - package="feast.types", - syntax="proto3", - serialized_options=_b( - "\n\013feast.typesB\014FeatureProtoZ6github.com/gojek/feast/protos/generated/go/feast/types" - ), - serialized_pb=_b( - '\n\x19\x66\x65\x61st/types/Feature.proto\x12\x0b\x66\x65\x61st.types\x1a\x17\x66\x65\x61st/types/Value.proto":\n\x07\x46\x65\x61ture\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.feast.types.Value\x12\x0c\n\x04name\x18\x03 \x01(\tBS\n\x0b\x66\x65\x61st.typesB\x0c\x46\x65\x61tureProtoZ6github.com/gojek/feast/protos/generated/go/feast/typesb\x06proto3' - ), - dependencies=[feast_dot_types_dot_Value__pb2.DESCRIPTOR], -) - - -_FEATURE = _descriptor.Descriptor( - name="Feature", - full_name="feast.types.Feature", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="value", - full_name="feast.types.Feature.value", - index=0, - number=2, - type=11, - cpp_type=10, - label=1, - has_default_value=False, - default_value=None, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="name", - full_name="feast.types.Feature.name", - index=1, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=67, - serialized_end=125, -) - -_FEATURE.fields_by_name["value"].message_type = feast_dot_types_dot_Value__pb2._VALUE -DESCRIPTOR.message_types_by_name["Feature"] = _FEATURE -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -Feature = _reflection.GeneratedProtocolMessageType( - "Feature", - (_message.Message,), - { - "DESCRIPTOR": _FEATURE, - "__module__": "feast.types.Feature_pb2" - # @@protoc_insertion_point(class_scope:feast.types.Feature) - }, -) -_sym_db.RegisterMessage(Feature) - - -DESCRIPTOR._options = None -# @@protoc_insertion_point(module_scope) diff --git a/sdk/python/feast/types/Feature_pb2.pyi b/sdk/python/feast/types/Feature_pb2.pyi deleted file mode 100644 index f31122f1da8..00000000000 --- a/sdk/python/feast/types/Feature_pb2.pyi +++ /dev/null @@ -1,44 +0,0 @@ -# @generated by generate_proto_mypy_stubs.py. Do not edit! -import sys -from feast.types.Value_pb2 import Value as feast___types___Value_pb2___Value - -from google.protobuf.descriptor import ( - Descriptor as google___protobuf___descriptor___Descriptor, -) - -from google.protobuf.message import Message as google___protobuf___message___Message - -from typing import Optional as typing___Optional, Text as typing___Text - -from typing_extensions import Literal as typing_extensions___Literal - -class Feature(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - name = ... # type: typing___Text - @property - def value(self) -> feast___types___Value_pb2___Value: ... - def __init__( - self, - *, - value: typing___Optional[feast___types___Value_pb2___Value] = None, - name: typing___Optional[typing___Text] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> Feature: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def HasField( - self, field_name: typing_extensions___Literal["value"] - ) -> bool: ... - def ClearField( - self, field_name: typing_extensions___Literal["name", "value"] - ) -> None: ... - else: - def HasField( - self, field_name: typing_extensions___Literal["value", b"value"] - ) -> bool: ... - def ClearField( - self, - field_name: typing_extensions___Literal["name", b"name", "value", b"value"], - ) -> None: ... diff --git a/sdk/python/feast/types/Field_pb2.py b/sdk/python/feast/types/Field_pb2.py deleted file mode 100644 index 95bcf38cf9d..00000000000 --- a/sdk/python/feast/types/Field_pb2.py +++ /dev/null @@ -1,81 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: feast/types/Field.proto - -import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from feast.types import Value_pb2 as feast_dot_types_dot_Value__pb2 - - -DESCRIPTOR = _descriptor.FileDescriptor( - name='feast/types/Field.proto', - package='feast.types', - syntax='proto3', - serialized_options=_b('\n\013feast.typesB\nFieldProtoZ0github.com/gojek/feast/sdk/go/protos/feast/types'), - serialized_pb=_b('\n\x17\x66\x65\x61st/types/Field.proto\x12\x0b\x66\x65\x61st.types\x1a\x17\x66\x65\x61st/types/Value.proto\"8\n\x05\x46ield\x12\x0c\n\x04name\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.feast.types.ValueBK\n\x0b\x66\x65\x61st.typesB\nFieldProtoZ0github.com/gojek/feast/sdk/go/protos/feast/typesb\x06proto3') - , - dependencies=[feast_dot_types_dot_Value__pb2.DESCRIPTOR,]) - - - - -_FIELD = _descriptor.Descriptor( - name='Field', - full_name='feast.types.Field', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='name', full_name='feast.types.Field.name', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='value', full_name='feast.types.Field.value', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=65, - serialized_end=121, -) - -_FIELD.fields_by_name['value'].message_type = feast_dot_types_dot_Value__pb2._VALUE -DESCRIPTOR.message_types_by_name['Field'] = _FIELD -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -Field = _reflection.GeneratedProtocolMessageType('Field', (_message.Message,), { - 'DESCRIPTOR' : _FIELD, - '__module__' : 'feast.types.Field_pb2' - # @@protoc_insertion_point(class_scope:feast.types.Field) - }) -_sym_db.RegisterMessage(Field) - - -DESCRIPTOR._options = None -# @@protoc_insertion_point(module_scope) diff --git a/sdk/python/feast/types/Field_pb2.pyi b/sdk/python/feast/types/Field_pb2.pyi deleted file mode 100644 index 1305503fab7..00000000000 --- a/sdk/python/feast/types/Field_pb2.pyi +++ /dev/null @@ -1,46 +0,0 @@ -# @generated by generate_proto_mypy_stubs.py. Do not edit! -import sys -from feast.types.Value_pb2 import ( - Value as feast___types___Value_pb2___Value, -) - -from google.protobuf.descriptor import ( - Descriptor as google___protobuf___descriptor___Descriptor, -) - -from google.protobuf.message import ( - Message as google___protobuf___message___Message, -) - -from typing import ( - Optional as typing___Optional, - Text as typing___Text, -) - -from typing_extensions import ( - Literal as typing_extensions___Literal, -) - - -class Field(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - name = ... # type: typing___Text - - @property - def value(self) -> feast___types___Value_pb2___Value: ... - - def __init__(self, - *, - name : typing___Optional[typing___Text] = None, - value : typing___Optional[feast___types___Value_pb2___Value] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> Field: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def HasField(self, field_name: typing_extensions___Literal[u"value"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"name",u"value"]) -> None: ... - else: - def HasField(self, field_name: typing_extensions___Literal[u"value",b"value"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"name",b"name",u"value",b"value"]) -> None: ... diff --git a/sdk/python/feast/types/Value_pb2.py b/sdk/python/feast/types/Value_pb2.py deleted file mode 100644 index fe2cd125ca5..00000000000 --- a/sdk/python/feast/types/Value_pb2.py +++ /dev/null @@ -1,595 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: feast/types/Value.proto - -import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor.FileDescriptor( - name='feast/types/Value.proto', - package='feast.types', - syntax='proto3', - serialized_options=_b('\n\013feast.typesB\nValueProtoZ0github.com/gojek/feast/sdk/go/protos/feast/types'), - serialized_pb=_b('\n\x17\x66\x65\x61st/types/Value.proto\x12\x0b\x66\x65\x61st.types\"\xe0\x01\n\tValueType\"\xd2\x01\n\x04\x45num\x12\x0b\n\x07INVALID\x10\x00\x12\t\n\x05\x42YTES\x10\x01\x12\n\n\x06STRING\x10\x02\x12\t\n\x05INT32\x10\x03\x12\t\n\x05INT64\x10\x04\x12\n\n\x06\x44OUBLE\x10\x05\x12\t\n\x05\x46LOAT\x10\x06\x12\x08\n\x04\x42OOL\x10\x07\x12\x0e\n\nBYTES_LIST\x10\x0b\x12\x0f\n\x0bSTRING_LIST\x10\x0c\x12\x0e\n\nINT32_LIST\x10\r\x12\x0e\n\nINT64_LIST\x10\x0e\x12\x0f\n\x0b\x44OUBLE_LIST\x10\x0f\x12\x0e\n\nFLOAT_LIST\x10\x10\x12\r\n\tBOOL_LIST\x10\x11\"\x82\x04\n\x05Value\x12\x13\n\tbytes_val\x18\x01 \x01(\x0cH\x00\x12\x14\n\nstring_val\x18\x02 \x01(\tH\x00\x12\x13\n\tint32_val\x18\x03 \x01(\x05H\x00\x12\x13\n\tint64_val\x18\x04 \x01(\x03H\x00\x12\x14\n\ndouble_val\x18\x05 \x01(\x01H\x00\x12\x13\n\tfloat_val\x18\x06 \x01(\x02H\x00\x12\x12\n\x08\x62ool_val\x18\x07 \x01(\x08H\x00\x12\x30\n\x0e\x62ytes_list_val\x18\x0b \x01(\x0b\x32\x16.feast.types.BytesListH\x00\x12\x32\n\x0fstring_list_val\x18\x0c \x01(\x0b\x32\x17.feast.types.StringListH\x00\x12\x30\n\x0eint32_list_val\x18\r \x01(\x0b\x32\x16.feast.types.Int32ListH\x00\x12\x30\n\x0eint64_list_val\x18\x0e \x01(\x0b\x32\x16.feast.types.Int64ListH\x00\x12\x32\n\x0f\x64ouble_list_val\x18\x0f \x01(\x0b\x32\x17.feast.types.DoubleListH\x00\x12\x30\n\x0e\x66loat_list_val\x18\x10 \x01(\x0b\x32\x16.feast.types.FloatListH\x00\x12.\n\rbool_list_val\x18\x11 \x01(\x0b\x32\x15.feast.types.BoolListH\x00\x42\x05\n\x03val\"\x18\n\tBytesList\x12\x0b\n\x03val\x18\x01 \x03(\x0c\"\x19\n\nStringList\x12\x0b\n\x03val\x18\x01 \x03(\t\"\x18\n\tInt32List\x12\x0b\n\x03val\x18\x01 \x03(\x05\"\x18\n\tInt64List\x12\x0b\n\x03val\x18\x01 \x03(\x03\"\x19\n\nDoubleList\x12\x0b\n\x03val\x18\x01 \x03(\x01\"\x18\n\tFloatList\x12\x0b\n\x03val\x18\x01 \x03(\x02\"\x17\n\x08\x42oolList\x12\x0b\n\x03val\x18\x01 \x03(\x08\x42K\n\x0b\x66\x65\x61st.typesB\nValueProtoZ0github.com/gojek/feast/sdk/go/protos/feast/typesb\x06proto3') -) - - - -_VALUETYPE_ENUM = _descriptor.EnumDescriptor( - name='Enum', - full_name='feast.types.ValueType.Enum', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='INVALID', index=0, number=0, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='BYTES', index=1, number=1, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='STRING', index=2, number=2, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='INT32', index=3, number=3, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='INT64', index=4, number=4, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='DOUBLE', index=5, number=5, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='FLOAT', index=6, number=6, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='BOOL', index=7, number=7, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='BYTES_LIST', index=8, number=11, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='STRING_LIST', index=9, number=12, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='INT32_LIST', index=10, number=13, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='INT64_LIST', index=11, number=14, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='DOUBLE_LIST', index=12, number=15, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='FLOAT_LIST', index=13, number=16, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='BOOL_LIST', index=14, number=17, - serialized_options=None, - type=None), - ], - containing_type=None, - serialized_options=None, - serialized_start=55, - serialized_end=265, -) -_sym_db.RegisterEnumDescriptor(_VALUETYPE_ENUM) - - -_VALUETYPE = _descriptor.Descriptor( - name='ValueType', - full_name='feast.types.ValueType', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - _VALUETYPE_ENUM, - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=41, - serialized_end=265, -) - - -_VALUE = _descriptor.Descriptor( - name='Value', - full_name='feast.types.Value', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='bytes_val', full_name='feast.types.Value.bytes_val', index=0, - number=1, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='string_val', full_name='feast.types.Value.string_val', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='int32_val', full_name='feast.types.Value.int32_val', index=2, - number=3, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='int64_val', full_name='feast.types.Value.int64_val', index=3, - number=4, type=3, cpp_type=2, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='double_val', full_name='feast.types.Value.double_val', index=4, - number=5, type=1, cpp_type=5, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='float_val', full_name='feast.types.Value.float_val', index=5, - number=6, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='bool_val', full_name='feast.types.Value.bool_val', index=6, - number=7, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='bytes_list_val', full_name='feast.types.Value.bytes_list_val', index=7, - number=11, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='string_list_val', full_name='feast.types.Value.string_list_val', index=8, - number=12, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='int32_list_val', full_name='feast.types.Value.int32_list_val', index=9, - number=13, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='int64_list_val', full_name='feast.types.Value.int64_list_val', index=10, - number=14, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='double_list_val', full_name='feast.types.Value.double_list_val', index=11, - number=15, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='float_list_val', full_name='feast.types.Value.float_list_val', index=12, - number=16, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='bool_list_val', full_name='feast.types.Value.bool_list_val', index=13, - number=17, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - _descriptor.OneofDescriptor( - name='val', full_name='feast.types.Value.val', - index=0, containing_type=None, fields=[]), - ], - serialized_start=268, - serialized_end=782, -) - - -_BYTESLIST = _descriptor.Descriptor( - name='BytesList', - full_name='feast.types.BytesList', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='val', full_name='feast.types.BytesList.val', index=0, - number=1, type=12, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=784, - serialized_end=808, -) - - -_STRINGLIST = _descriptor.Descriptor( - name='StringList', - full_name='feast.types.StringList', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='val', full_name='feast.types.StringList.val', index=0, - number=1, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=810, - serialized_end=835, -) - - -_INT32LIST = _descriptor.Descriptor( - name='Int32List', - full_name='feast.types.Int32List', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='val', full_name='feast.types.Int32List.val', index=0, - number=1, type=5, cpp_type=1, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=837, - serialized_end=861, -) - - -_INT64LIST = _descriptor.Descriptor( - name='Int64List', - full_name='feast.types.Int64List', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='val', full_name='feast.types.Int64List.val', index=0, - number=1, type=3, cpp_type=2, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=863, - serialized_end=887, -) - - -_DOUBLELIST = _descriptor.Descriptor( - name='DoubleList', - full_name='feast.types.DoubleList', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='val', full_name='feast.types.DoubleList.val', index=0, - number=1, type=1, cpp_type=5, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=889, - serialized_end=914, -) - - -_FLOATLIST = _descriptor.Descriptor( - name='FloatList', - full_name='feast.types.FloatList', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='val', full_name='feast.types.FloatList.val', index=0, - number=1, type=2, cpp_type=6, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=916, - serialized_end=940, -) - - -_BOOLLIST = _descriptor.Descriptor( - name='BoolList', - full_name='feast.types.BoolList', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='val', full_name='feast.types.BoolList.val', index=0, - number=1, type=8, cpp_type=7, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=942, - serialized_end=965, -) - -_VALUETYPE_ENUM.containing_type = _VALUETYPE -_VALUE.fields_by_name['bytes_list_val'].message_type = _BYTESLIST -_VALUE.fields_by_name['string_list_val'].message_type = _STRINGLIST -_VALUE.fields_by_name['int32_list_val'].message_type = _INT32LIST -_VALUE.fields_by_name['int64_list_val'].message_type = _INT64LIST -_VALUE.fields_by_name['double_list_val'].message_type = _DOUBLELIST -_VALUE.fields_by_name['float_list_val'].message_type = _FLOATLIST -_VALUE.fields_by_name['bool_list_val'].message_type = _BOOLLIST -_VALUE.oneofs_by_name['val'].fields.append( - _VALUE.fields_by_name['bytes_val']) -_VALUE.fields_by_name['bytes_val'].containing_oneof = _VALUE.oneofs_by_name['val'] -_VALUE.oneofs_by_name['val'].fields.append( - _VALUE.fields_by_name['string_val']) -_VALUE.fields_by_name['string_val'].containing_oneof = _VALUE.oneofs_by_name['val'] -_VALUE.oneofs_by_name['val'].fields.append( - _VALUE.fields_by_name['int32_val']) -_VALUE.fields_by_name['int32_val'].containing_oneof = _VALUE.oneofs_by_name['val'] -_VALUE.oneofs_by_name['val'].fields.append( - _VALUE.fields_by_name['int64_val']) -_VALUE.fields_by_name['int64_val'].containing_oneof = _VALUE.oneofs_by_name['val'] -_VALUE.oneofs_by_name['val'].fields.append( - _VALUE.fields_by_name['double_val']) -_VALUE.fields_by_name['double_val'].containing_oneof = _VALUE.oneofs_by_name['val'] -_VALUE.oneofs_by_name['val'].fields.append( - _VALUE.fields_by_name['float_val']) -_VALUE.fields_by_name['float_val'].containing_oneof = _VALUE.oneofs_by_name['val'] -_VALUE.oneofs_by_name['val'].fields.append( - _VALUE.fields_by_name['bool_val']) -_VALUE.fields_by_name['bool_val'].containing_oneof = _VALUE.oneofs_by_name['val'] -_VALUE.oneofs_by_name['val'].fields.append( - _VALUE.fields_by_name['bytes_list_val']) -_VALUE.fields_by_name['bytes_list_val'].containing_oneof = _VALUE.oneofs_by_name['val'] -_VALUE.oneofs_by_name['val'].fields.append( - _VALUE.fields_by_name['string_list_val']) -_VALUE.fields_by_name['string_list_val'].containing_oneof = _VALUE.oneofs_by_name['val'] -_VALUE.oneofs_by_name['val'].fields.append( - _VALUE.fields_by_name['int32_list_val']) -_VALUE.fields_by_name['int32_list_val'].containing_oneof = _VALUE.oneofs_by_name['val'] -_VALUE.oneofs_by_name['val'].fields.append( - _VALUE.fields_by_name['int64_list_val']) -_VALUE.fields_by_name['int64_list_val'].containing_oneof = _VALUE.oneofs_by_name['val'] -_VALUE.oneofs_by_name['val'].fields.append( - _VALUE.fields_by_name['double_list_val']) -_VALUE.fields_by_name['double_list_val'].containing_oneof = _VALUE.oneofs_by_name['val'] -_VALUE.oneofs_by_name['val'].fields.append( - _VALUE.fields_by_name['float_list_val']) -_VALUE.fields_by_name['float_list_val'].containing_oneof = _VALUE.oneofs_by_name['val'] -_VALUE.oneofs_by_name['val'].fields.append( - _VALUE.fields_by_name['bool_list_val']) -_VALUE.fields_by_name['bool_list_val'].containing_oneof = _VALUE.oneofs_by_name['val'] -DESCRIPTOR.message_types_by_name['ValueType'] = _VALUETYPE -DESCRIPTOR.message_types_by_name['Value'] = _VALUE -DESCRIPTOR.message_types_by_name['BytesList'] = _BYTESLIST -DESCRIPTOR.message_types_by_name['StringList'] = _STRINGLIST -DESCRIPTOR.message_types_by_name['Int32List'] = _INT32LIST -DESCRIPTOR.message_types_by_name['Int64List'] = _INT64LIST -DESCRIPTOR.message_types_by_name['DoubleList'] = _DOUBLELIST -DESCRIPTOR.message_types_by_name['FloatList'] = _FLOATLIST -DESCRIPTOR.message_types_by_name['BoolList'] = _BOOLLIST -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -ValueType = _reflection.GeneratedProtocolMessageType('ValueType', (_message.Message,), { - 'DESCRIPTOR' : _VALUETYPE, - '__module__' : 'feast.types.Value_pb2' - # @@protoc_insertion_point(class_scope:feast.types.ValueType) - }) -_sym_db.RegisterMessage(ValueType) - -Value = _reflection.GeneratedProtocolMessageType('Value', (_message.Message,), { - 'DESCRIPTOR' : _VALUE, - '__module__' : 'feast.types.Value_pb2' - # @@protoc_insertion_point(class_scope:feast.types.Value) - }) -_sym_db.RegisterMessage(Value) - -BytesList = _reflection.GeneratedProtocolMessageType('BytesList', (_message.Message,), { - 'DESCRIPTOR' : _BYTESLIST, - '__module__' : 'feast.types.Value_pb2' - # @@protoc_insertion_point(class_scope:feast.types.BytesList) - }) -_sym_db.RegisterMessage(BytesList) - -StringList = _reflection.GeneratedProtocolMessageType('StringList', (_message.Message,), { - 'DESCRIPTOR' : _STRINGLIST, - '__module__' : 'feast.types.Value_pb2' - # @@protoc_insertion_point(class_scope:feast.types.StringList) - }) -_sym_db.RegisterMessage(StringList) - -Int32List = _reflection.GeneratedProtocolMessageType('Int32List', (_message.Message,), { - 'DESCRIPTOR' : _INT32LIST, - '__module__' : 'feast.types.Value_pb2' - # @@protoc_insertion_point(class_scope:feast.types.Int32List) - }) -_sym_db.RegisterMessage(Int32List) - -Int64List = _reflection.GeneratedProtocolMessageType('Int64List', (_message.Message,), { - 'DESCRIPTOR' : _INT64LIST, - '__module__' : 'feast.types.Value_pb2' - # @@protoc_insertion_point(class_scope:feast.types.Int64List) - }) -_sym_db.RegisterMessage(Int64List) - -DoubleList = _reflection.GeneratedProtocolMessageType('DoubleList', (_message.Message,), { - 'DESCRIPTOR' : _DOUBLELIST, - '__module__' : 'feast.types.Value_pb2' - # @@protoc_insertion_point(class_scope:feast.types.DoubleList) - }) -_sym_db.RegisterMessage(DoubleList) - -FloatList = _reflection.GeneratedProtocolMessageType('FloatList', (_message.Message,), { - 'DESCRIPTOR' : _FLOATLIST, - '__module__' : 'feast.types.Value_pb2' - # @@protoc_insertion_point(class_scope:feast.types.FloatList) - }) -_sym_db.RegisterMessage(FloatList) - -BoolList = _reflection.GeneratedProtocolMessageType('BoolList', (_message.Message,), { - 'DESCRIPTOR' : _BOOLLIST, - '__module__' : 'feast.types.Value_pb2' - # @@protoc_insertion_point(class_scope:feast.types.BoolList) - }) -_sym_db.RegisterMessage(BoolList) - - -DESCRIPTOR._options = None -# @@protoc_insertion_point(module_scope) diff --git a/sdk/python/feast/types/Value_pb2.pyi b/sdk/python/feast/types/Value_pb2.pyi deleted file mode 100644 index d8b8a73dd36..00000000000 --- a/sdk/python/feast/types/Value_pb2.pyi +++ /dev/null @@ -1,260 +0,0 @@ -# @generated by generate_proto_mypy_stubs.py. Do not edit! -import sys -from google.protobuf.descriptor import ( - Descriptor as google___protobuf___descriptor___Descriptor, - EnumDescriptor as google___protobuf___descriptor___EnumDescriptor, -) - -from google.protobuf.internal.containers import ( - RepeatedScalarFieldContainer as google___protobuf___internal___containers___RepeatedScalarFieldContainer, -) - -from google.protobuf.message import ( - Message as google___protobuf___message___Message, -) - -from typing import ( - Iterable as typing___Iterable, - List as typing___List, - Optional as typing___Optional, - Text as typing___Text, - Tuple as typing___Tuple, - cast as typing___cast, -) - -from typing_extensions import ( - Literal as typing_extensions___Literal, -) - - -class ValueType(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - class Enum(int): - DESCRIPTOR: google___protobuf___descriptor___EnumDescriptor = ... - @classmethod - def Name(cls, number: int) -> str: ... - @classmethod - def Value(cls, name: str) -> ValueType.Enum: ... - @classmethod - def keys(cls) -> typing___List[str]: ... - @classmethod - def values(cls) -> typing___List[ValueType.Enum]: ... - @classmethod - def items(cls) -> typing___List[typing___Tuple[str, ValueType.Enum]]: ... - INVALID = typing___cast(ValueType.Enum, 0) - BYTES = typing___cast(ValueType.Enum, 1) - STRING = typing___cast(ValueType.Enum, 2) - INT32 = typing___cast(ValueType.Enum, 3) - INT64 = typing___cast(ValueType.Enum, 4) - DOUBLE = typing___cast(ValueType.Enum, 5) - FLOAT = typing___cast(ValueType.Enum, 6) - BOOL = typing___cast(ValueType.Enum, 7) - BYTES_LIST = typing___cast(ValueType.Enum, 11) - STRING_LIST = typing___cast(ValueType.Enum, 12) - INT32_LIST = typing___cast(ValueType.Enum, 13) - INT64_LIST = typing___cast(ValueType.Enum, 14) - DOUBLE_LIST = typing___cast(ValueType.Enum, 15) - FLOAT_LIST = typing___cast(ValueType.Enum, 16) - BOOL_LIST = typing___cast(ValueType.Enum, 17) - INVALID = typing___cast(ValueType.Enum, 0) - BYTES = typing___cast(ValueType.Enum, 1) - STRING = typing___cast(ValueType.Enum, 2) - INT32 = typing___cast(ValueType.Enum, 3) - INT64 = typing___cast(ValueType.Enum, 4) - DOUBLE = typing___cast(ValueType.Enum, 5) - FLOAT = typing___cast(ValueType.Enum, 6) - BOOL = typing___cast(ValueType.Enum, 7) - BYTES_LIST = typing___cast(ValueType.Enum, 11) - STRING_LIST = typing___cast(ValueType.Enum, 12) - INT32_LIST = typing___cast(ValueType.Enum, 13) - INT64_LIST = typing___cast(ValueType.Enum, 14) - DOUBLE_LIST = typing___cast(ValueType.Enum, 15) - FLOAT_LIST = typing___cast(ValueType.Enum, 16) - BOOL_LIST = typing___cast(ValueType.Enum, 17) - - - def __init__(self, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> ValueType: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - -class Value(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - bytes_val = ... # type: bytes - string_val = ... # type: typing___Text - int32_val = ... # type: int - int64_val = ... # type: int - double_val = ... # type: float - float_val = ... # type: float - bool_val = ... # type: bool - - @property - def bytes_list_val(self) -> BytesList: ... - - @property - def string_list_val(self) -> StringList: ... - - @property - def int32_list_val(self) -> Int32List: ... - - @property - def int64_list_val(self) -> Int64List: ... - - @property - def double_list_val(self) -> DoubleList: ... - - @property - def float_list_val(self) -> FloatList: ... - - @property - def bool_list_val(self) -> BoolList: ... - - def __init__(self, - *, - bytes_val : typing___Optional[bytes] = None, - string_val : typing___Optional[typing___Text] = None, - int32_val : typing___Optional[int] = None, - int64_val : typing___Optional[int] = None, - double_val : typing___Optional[float] = None, - float_val : typing___Optional[float] = None, - bool_val : typing___Optional[bool] = None, - bytes_list_val : typing___Optional[BytesList] = None, - string_list_val : typing___Optional[StringList] = None, - int32_list_val : typing___Optional[Int32List] = None, - int64_list_val : typing___Optional[Int64List] = None, - double_list_val : typing___Optional[DoubleList] = None, - float_list_val : typing___Optional[FloatList] = None, - bool_list_val : typing___Optional[BoolList] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> Value: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def HasField(self, field_name: typing_extensions___Literal[u"bool_list_val",u"bool_val",u"bytes_list_val",u"bytes_val",u"double_list_val",u"double_val",u"float_list_val",u"float_val",u"int32_list_val",u"int32_val",u"int64_list_val",u"int64_val",u"string_list_val",u"string_val",u"val"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"bool_list_val",u"bool_val",u"bytes_list_val",u"bytes_val",u"double_list_val",u"double_val",u"float_list_val",u"float_val",u"int32_list_val",u"int32_val",u"int64_list_val",u"int64_val",u"string_list_val",u"string_val",u"val"]) -> None: ... - else: - def HasField(self, field_name: typing_extensions___Literal[u"bool_list_val",b"bool_list_val",u"bool_val",b"bool_val",u"bytes_list_val",b"bytes_list_val",u"bytes_val",b"bytes_val",u"double_list_val",b"double_list_val",u"double_val",b"double_val",u"float_list_val",b"float_list_val",u"float_val",b"float_val",u"int32_list_val",b"int32_list_val",u"int32_val",b"int32_val",u"int64_list_val",b"int64_list_val",u"int64_val",b"int64_val",u"string_list_val",b"string_list_val",u"string_val",b"string_val",u"val",b"val"]) -> bool: ... - def ClearField(self, field_name: typing_extensions___Literal[u"bool_list_val",b"bool_list_val",u"bool_val",b"bool_val",u"bytes_list_val",b"bytes_list_val",u"bytes_val",b"bytes_val",u"double_list_val",b"double_list_val",u"double_val",b"double_val",u"float_list_val",b"float_list_val",u"float_val",b"float_val",u"int32_list_val",b"int32_list_val",u"int32_val",b"int32_val",u"int64_list_val",b"int64_list_val",u"int64_val",b"int64_val",u"string_list_val",b"string_list_val",u"string_val",b"string_val",u"val",b"val"]) -> None: ... - def WhichOneof(self, oneof_group: typing_extensions___Literal[u"val",b"val"]) -> typing_extensions___Literal["bytes_val","string_val","int32_val","int64_val","double_val","float_val","bool_val","bytes_list_val","string_list_val","int32_list_val","int64_list_val","double_list_val","float_list_val","bool_list_val"]: ... - -class BytesList(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - val = ... # type: google___protobuf___internal___containers___RepeatedScalarFieldContainer[bytes] - - def __init__(self, - *, - val : typing___Optional[typing___Iterable[bytes]] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> BytesList: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"val"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"val",b"val"]) -> None: ... - -class StringList(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - val = ... # type: google___protobuf___internal___containers___RepeatedScalarFieldContainer[typing___Text] - - def __init__(self, - *, - val : typing___Optional[typing___Iterable[typing___Text]] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> StringList: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"val"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"val",b"val"]) -> None: ... - -class Int32List(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - val = ... # type: google___protobuf___internal___containers___RepeatedScalarFieldContainer[int] - - def __init__(self, - *, - val : typing___Optional[typing___Iterable[int]] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> Int32List: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"val"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"val",b"val"]) -> None: ... - -class Int64List(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - val = ... # type: google___protobuf___internal___containers___RepeatedScalarFieldContainer[int] - - def __init__(self, - *, - val : typing___Optional[typing___Iterable[int]] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> Int64List: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"val"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"val",b"val"]) -> None: ... - -class DoubleList(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - val = ... # type: google___protobuf___internal___containers___RepeatedScalarFieldContainer[float] - - def __init__(self, - *, - val : typing___Optional[typing___Iterable[float]] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> DoubleList: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"val"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"val",b"val"]) -> None: ... - -class FloatList(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - val = ... # type: google___protobuf___internal___containers___RepeatedScalarFieldContainer[float] - - def __init__(self, - *, - val : typing___Optional[typing___Iterable[float]] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> FloatList: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"val"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"val",b"val"]) -> None: ... - -class BoolList(google___protobuf___message___Message): - DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... - val = ... # type: google___protobuf___internal___containers___RepeatedScalarFieldContainer[bool] - - def __init__(self, - *, - val : typing___Optional[typing___Iterable[bool]] = None, - ) -> None: ... - @classmethod - def FromString(cls, s: bytes) -> BoolList: ... - def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... - if sys.version_info >= (3,): - def ClearField(self, field_name: typing_extensions___Literal[u"val"]) -> None: ... - else: - def ClearField(self, field_name: typing_extensions___Literal[u"val",b"val"]) -> None: ... diff --git a/sdk/python/feast/types/__init__.py b/sdk/python/feast/types/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/sdk/python/feast/value_type.py b/sdk/python/feast/value_type.py index df315480ce7..687dccc7b7f 100644 --- a/sdk/python/feast/value_type.py +++ b/sdk/python/feast/value_type.py @@ -14,6 +14,8 @@ import enum +from tensorflow_metadata.proto.v0 import schema_pb2 + class ValueType(enum.Enum): """ @@ -35,3 +37,24 @@ class ValueType(enum.Enum): DOUBLE_LIST = 15 FLOAT_LIST = 16 BOOL_LIST = 17 + + def to_tfx_schema_feature_type(self) -> schema_pb2.FeatureType: + if self.value in [ + ValueType.BYTES.value, + ValueType.STRING.value, + ValueType.BOOL.value, + ValueType.BYTES_LIST.value, + ValueType.STRING_LIST.value, + ValueType.INT32_LIST.value, + ValueType.INT64_LIST.value, + ValueType.DOUBLE_LIST.value, + ValueType.FLOAT_LIST.value, + ValueType.BOOL_LIST.value, + ]: + return schema_pb2.FeatureType.BYTES + elif self.value in [ValueType.INT32.value, ValueType.INT64.value]: + return schema_pb2.FeatureType.INT + elif self.value in [ValueType.DOUBLE.value, ValueType.FLOAT.value]: + return schema_pb2.FeatureType.FLOAT + else: + return schema_pb2.FeatureType.TYPE_UNKNOWN diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml new file mode 100644 index 00000000000..12073607100 --- /dev/null +++ b/sdk/python/pyproject.toml @@ -0,0 +1,26 @@ +[tool.black] +line-length = 88 +target-version = ['py37'] +include = '\.pyi?$' +exclude = ''' +( + /( + \.eggs # exclude a few common directories in the + | \.git # root of the project + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist + | pb2.py + | \.pyi + | core + | serving + | storage + | types + )/ +) +''' \ No newline at end of file diff --git a/sdk/python/requirements-ci.txt b/sdk/python/requirements-ci.txt index f3df60a02ec..45aff4788b4 100644 --- a/sdk/python/requirements-ci.txt +++ b/sdk/python/requirements-ci.txt @@ -1,22 +1,13 @@ -google-api-core==1.* -google-auth==1.* -google-cloud-bigquery==1.* -google-cloud-bigquery-storage==0.* -google-cloud-storage==1.* -google-resumable-media>=0.5 -googleapis-common-protos==1.* -grpcio==1.* -numpy -mock==2.0.0 -pandas==0.* -protobuf==3.* +flake8 +black +isort +grpcio-tools +mypy-protobuf pytest +pytest-lazy-fixture==0.6.3 pytest-mock pytest-timeout -PyYAML==5.1.2 -fastavro==0.21.* -grpcio-testing==1.* pytest-ordering==0.6.* -pyarrow -Sphinx -sphinx-rtd-theme \ No newline at end of file +pandas==0.* +mock==2.0.0 +pandavro==1.5.* \ No newline at end of file diff --git a/sdk/python/requirements-dev.txt b/sdk/python/requirements-dev.txt new file mode 100644 index 00000000000..f24141fb491 --- /dev/null +++ b/sdk/python/requirements-dev.txt @@ -0,0 +1,38 @@ +Click==7.* +google-api-core==1.* +google-auth==1.* +google-cloud-bigquery==1.* +google-cloud-bigquery-storage==0.* +google-cloud-storage==1.* +google-resumable-media>=0.5 +googleapis-common-protos==1.* +grpcio==1.* +grpcio-testing==1.* +grpcio-tools +numpy +mock==2.0.0 +pandas==0.* +protobuf==3.* +pytest +pytest-lazy-fixture==0.6.3 +pytest-mock +pytest-timeout +pytest-ordering==0.6.* +PyYAML==5.1.* +fastavro==0.* +pyarrow +Sphinx +sphinx-rtd-theme +toml==0.10.* +tqdm==4.* +confluent_kafka +google +pandavro==1.5.* +kafka-python==1.* +tabulate==0.8.* +isort +mypy +mypy-protobuf +pre-commit +flake8 +black \ No newline at end of file diff --git a/sdk/python/setup.cfg b/sdk/python/setup.cfg new file mode 100644 index 00000000000..9ccd3bb57aa --- /dev/null +++ b/sdk/python/setup.cfg @@ -0,0 +1,18 @@ +[isort] +multi_line_output=3 +include_trailing_comma=True +force_grid_wrap=0 +use_parentheses=True +line_length=88 +skip=feast/types,feast/core,feast/serving,feast/storage + +[flake8] +ignore = E203, E266, E501, W503 +max-line-length = 88 +max-complexity = 20 +select = B,C,E,F,W,T4 +exclude = .git,__pycache__,docs/conf.py,dist,feast/core,feast/serving,feast/types,feast/storage + +[mypy] +files=feast,test +ignore_missing_imports=true \ No newline at end of file diff --git a/sdk/python/setup.py b/sdk/python/setup.py index 1617f83852f..69ea44a1871 100644 --- a/sdk/python/setup.py +++ b/sdk/python/setup.py @@ -13,12 +13,13 @@ # limitations under the License. import os +import subprocess from setuptools import find_packages, setup NAME = "feast" DESCRIPTION = "Python SDK for Feast" -URL = "https://github.com/gojek/feast" +URL = "https://github.com/feast-dev/feast" AUTHOR = "Feast" REQUIRES_PYTHON = ">=3.6.0" @@ -36,7 +37,7 @@ "pandavro==1.5.*", "protobuf>=3.10", "PyYAML==5.1.*", - "fastavro==0.*", + "fastavro>=0.22.11,<0.23", "kafka-python==1.*", "tabulate==0.8.*", "toml==0.10.*", @@ -48,7 +49,13 @@ ] # README file from Feast repo root directory -README_FILE = os.path.join(os.path.dirname(__file__), "..", "..", "README.md") +repo_root = ( + subprocess.Popen(["git", "rev-parse", "--show-toplevel"], stdout=subprocess.PIPE) + .communicate()[0] + .rstrip() + .decode("utf-8") +) +README_FILE = os.path.join(repo_root, "README.md") with open(os.path.join(README_FILE), "r") as f: LONG_DESCRIPTION = f.read() diff --git a/sdk/python/tensorflow_metadata/__init__.py b/sdk/python/tensorflow_metadata/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/sdk/python/tensorflow_metadata/proto/__init__.py b/sdk/python/tensorflow_metadata/proto/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/sdk/python/tensorflow_metadata/proto/v0/__init__.py b/sdk/python/tensorflow_metadata/proto/v0/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/sdk/python/tests/conftest.py b/sdk/python/tests/conftest.py new file mode 100644 index 00000000000..5803f0b0c07 --- /dev/null +++ b/sdk/python/tests/conftest.py @@ -0,0 +1,22 @@ +# Copyright 2019 The Feast Authors +# +# 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. +import multiprocessing +from sys import platform + + +def pytest_configure(config): + if platform in ["darwin", "windows"]: + multiprocessing.set_start_method("spawn") + else: + multiprocessing.set_start_method("fork") diff --git a/sdk/python/tests/data/localhost.crt b/sdk/python/tests/data/localhost.crt new file mode 100644 index 00000000000..1f471506aab --- /dev/null +++ b/sdk/python/tests/data/localhost.crt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC5zCCAc+gAwIBAgIJAKzukpnyuwsVMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV +BAMMCWxvY2FsaG9zdDAgFw0yMDAyMTcxMTE4NDNaGA8zMDE5MDYyMDExMTg0M1ow +FDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAqoanhiy4EUZjPA/m8IWk50OyTjKAnqZvEW5glqmTHP6lQbfyWQnzj3Ny +c++4Xn901FO2v07h+7lE3BScjgCX6klsLOHRnWcLX8lQygR6zzO+Oey1yXuCebBA +yhrsqgTDC/8zoCxe0W3t0vqvE4AJs3tJHq5Y1ba/X9OiKKsDZuMSSsbdd4qVEL6y +BD8PRNLT/iiD84Kq58GZtOI3fJls8E/bYbvksugcPI3kmlU4Plg3VrVplMl3DcMz +7BbvQP6jmVqdPtUT7+lL0C5CsNqbdDOIwg09+Gwus+A/g8PerBBd+ZCmdvSa9LYJ +OmlJszgZPIL9AagXLfuGQvNN2Y6WowIDAQABozowODAUBgNVHREEDTALgglsb2Nh +bGhvc3QwCwYDVR0PBAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0GCSqGSIb3 +DQEBCwUAA4IBAQAuF1/VeQL73Y1FKrBX4bAb/Rdh2+Dadpi+w1pgEOi3P4udmQ+y +Xn9GwwLRQmHRLjyCT5KT8lNHdldPdlBamqPGGku449aCAjA/YHVHhcHaXl0MtPGq +BfKhHYSsvI2sIymlzZIvvIaf04yuJ1g+L0j8Px4Ecor9YwcKDZmpnIXLgdUtUrIQ +5Omrb4jImX6q8jp6Bjplb4H3o4TqKoa74NLOWUiH5/Rix3Lo8MRoEVbX2GhKk+8n +0eD3AuyrI1i+ce7zY8qGJKKFHGLDWPA/+006ZIS4j/Hr2FWo07CPFQ4/3gdJ8Erw +SzgO9vvIhQrBJn2CIH4+P5Cb1ktdobNWW9XK +-----END CERTIFICATE----- diff --git a/sdk/python/tests/data/localhost.key b/sdk/python/tests/data/localhost.key new file mode 100644 index 00000000000..dbd9cda062c --- /dev/null +++ b/sdk/python/tests/data/localhost.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCqhqeGLLgRRmM8 +D+bwhaTnQ7JOMoCepm8RbmCWqZMc/qVBt/JZCfOPc3Jz77hef3TUU7a/TuH7uUTc +FJyOAJfqSWws4dGdZwtfyVDKBHrPM7457LXJe4J5sEDKGuyqBMML/zOgLF7Rbe3S ++q8TgAmze0kerljVtr9f06IoqwNm4xJKxt13ipUQvrIEPw9E0tP+KIPzgqrnwZm0 +4jd8mWzwT9thu+Sy6Bw8jeSaVTg+WDdWtWmUyXcNwzPsFu9A/qOZWp0+1RPv6UvQ +LkKw2pt0M4jCDT34bC6z4D+Dw96sEF35kKZ29Jr0tgk6aUmzOBk8gv0BqBct+4ZC +803ZjpajAgMBAAECggEADE4FHphxe8WheX8IQgjSumFXJ29bepc14oMdcyGvXOM/ +F3vnf+dI7Ov+sUD2A9OcoYmc4TcW9WwL/Pl7xn9iduRvatmsn3gFCRdkvf8OwY7R +Riq/f1drNc6zDiJdO3N2g5IZrpAlE2WkSJoQMg8GJC5cO1uHS3yRWJ/Tzq1wZGcW +Dot9hAFgN0qNdP0xFkOsPM5ptC3DjLqsZWboJhIM19hgsIYaWQWHvcYlCcWTVhkj +FYzvLj5GrzAgyE89RpdXus670q5E2R2Rlnja21TfcxK0UOdIrKghZ0jxZMsXEwdB +8V7kIzL5kh//RhT/dIt0mHNMSdLFFx3yMTb2wTzpWQKBgQDRiCRslDSjiNSFySkn +6IivAwJtV2gLSxV05D9u9lrrlskHogrZUJkpVF1VzSnwv/ASaCZX4AGTtNPaz+vy +yDviwfjADsuum8jkzoxKCHnR1HVMyX+vm/g+pE20PMskTUuDE4zROtrqo9Ky0afv +94mJrf93Q815rsbEM5osugaeBQKBgQDQWAPTKy1wcG7edwfu3EaLYHPZ8pW9MldP +FvCLTMwSDkSzU+wA4BGE/5Tuu0WHSAfUc5C1LnMQXKBQXun+YCaBR6GZjUAmntz3 +poBIOYaxe651zqzCmo4ip1h5wIfPvynsyGmhsbpDSNhvXFgH2mF3XSY1nduKSRHu +389cHk3ahwKBgA4gAWSYcRv9I2aJcw7PrDcwGr/IPqlUPHQO1v/h96seFRtAnz6b +IlgY6dnY5NTn+4UiJEOUREbyz71Weu949CCLNvurg6uXsOlLy0VKYPv2OJoek08B +UrDWXq6h0of19fs2HC4Wq59Zv+ByJcIVi94OLsSZe4aSc6/SUrhlKgEJAoGBAIvR +5Y88NNx2uBEYdPx6W+WBr34e7Rrxw+JSFNCHk5SyeqyWr5XOyjMliv/EMl8dmhOc +Ewtkxte+MeB+Mi8CvBSay/rO7rR8fPK+jOzrnldSF7z8HLjlHGppQFlFOl/TfQFp +ZmqbadNp+caShImQp0SCAPiOnh1p+F0FWpYJyFnVAoGAKhSRP0iUmd+tId94px2m +G248BhcM9/0r+Y3yRX1eBx5eBzlzPUPcW1MSbhiZ1DIyLZ/MyObl98A1oNBGun11 +H/7Mq0E8BcJoXmt/6Z+2NhREBV9tDNuINyS/coYBV7H50pnSqyPpREPxNmu3Ukbm +u7ggLRfH+DexDysbpbCZ9l4= +-----END PRIVATE KEY----- diff --git a/sdk/python/tests/data/localhost.pem b/sdk/python/tests/data/localhost.pem new file mode 100644 index 00000000000..1f471506aab --- /dev/null +++ b/sdk/python/tests/data/localhost.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC5zCCAc+gAwIBAgIJAKzukpnyuwsVMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV +BAMMCWxvY2FsaG9zdDAgFw0yMDAyMTcxMTE4NDNaGA8zMDE5MDYyMDExMTg0M1ow +FDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAqoanhiy4EUZjPA/m8IWk50OyTjKAnqZvEW5glqmTHP6lQbfyWQnzj3Ny +c++4Xn901FO2v07h+7lE3BScjgCX6klsLOHRnWcLX8lQygR6zzO+Oey1yXuCebBA +yhrsqgTDC/8zoCxe0W3t0vqvE4AJs3tJHq5Y1ba/X9OiKKsDZuMSSsbdd4qVEL6y +BD8PRNLT/iiD84Kq58GZtOI3fJls8E/bYbvksugcPI3kmlU4Plg3VrVplMl3DcMz +7BbvQP6jmVqdPtUT7+lL0C5CsNqbdDOIwg09+Gwus+A/g8PerBBd+ZCmdvSa9LYJ +OmlJszgZPIL9AagXLfuGQvNN2Y6WowIDAQABozowODAUBgNVHREEDTALgglsb2Nh +bGhvc3QwCwYDVR0PBAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0GCSqGSIb3 +DQEBCwUAA4IBAQAuF1/VeQL73Y1FKrBX4bAb/Rdh2+Dadpi+w1pgEOi3P4udmQ+y +Xn9GwwLRQmHRLjyCT5KT8lNHdldPdlBamqPGGku449aCAjA/YHVHhcHaXl0MtPGq +BfKhHYSsvI2sIymlzZIvvIaf04yuJ1g+L0j8Px4Ecor9YwcKDZmpnIXLgdUtUrIQ +5Omrb4jImX6q8jp6Bjplb4H3o4TqKoa74NLOWUiH5/Rix3Lo8MRoEVbX2GhKk+8n +0eD3AuyrI1i+ce7zY8qGJKKFHGLDWPA/+006ZIS4j/Hr2FWo07CPFQ4/3gdJ8Erw +SzgO9vvIhQrBJn2CIH4+P5Cb1ktdobNWW9XK +-----END CERTIFICATE----- diff --git a/sdk/python/tests/data/tensorflow_metadata/bikeshare_feature_set.yaml b/sdk/python/tests/data/tensorflow_metadata/bikeshare_feature_set.yaml new file mode 100644 index 00000000000..48c595712cb --- /dev/null +++ b/sdk/python/tests/data/tensorflow_metadata/bikeshare_feature_set.yaml @@ -0,0 +1,72 @@ +spec: + name: bikeshare + entities: + - name: station_id + valueType: INT64 + features: + - name: location + valueType: STRING + stringDomain: + name: location + value: + - (30.24258, -97.71726) + - (30.24472, -97.72336) + - (30.24891, -97.75019) + presence: + minFraction: 1.0 + minCount: 1 + shape: + dim: + - size: 1 + - name: name + valueType: STRING + stringDomain: + name: name + value: + - 10th & Red River + - 11th & Salina + - 11th & San Jacinto + - 13th & San Antonio + - 17th & Guadalupe + presence: + minFraction: 1.0 + minCount: 1 + shape: + dim: + - size: 1 + - name: status + valueType: STRING + stringDomain: + name: status + value: + - "active" + - "closed" + presence: + minFraction: 1.0 + minCount: 1 + shape: + dim: + - size: 1 + - name: latitude + valueType: DOUBLE + floatDomain: + min: 100.0 + max: 105.0 + presence: + minFraction: 1.0 + minCount: 1 + shape: + dim: + - size: 1 + - name: longitude + valueType: DOUBLE + floatDomain: + min: 102.0 + max: 105.0 + presence: + minFraction: 1.0 + minCount: 1 + shape: + dim: + - size: 1 + maxAge: 3600s diff --git a/sdk/python/tests/data/tensorflow_metadata/bikeshare_schema.json b/sdk/python/tests/data/tensorflow_metadata/bikeshare_schema.json new file mode 100644 index 00000000000..fa9f97cca0d --- /dev/null +++ b/sdk/python/tests/data/tensorflow_metadata/bikeshare_schema.json @@ -0,0 +1,117 @@ +{ + "feature": [ + { + "name": "location", + "type": "BYTES", + "domain": "location", + "presence": { + "minFraction": 1.0, + "minCount": "1" + }, + "shape": { + "dim": [ + { + "size": "1" + } + ] + } + }, + { + "name": "name", + "type": "BYTES", + "domain": "name", + "presence": { + "minFraction": 1.0, + "minCount": "1" + }, + "shape": { + "dim": [ + { + "size": "1" + } + ] + } + }, + { + "name": "status", + "type": "BYTES", + "domain": "status", + "presence": { + "minFraction": 1.0, + "minCount": "1" + }, + "shape": { + "dim": [ + { + "size": "1" + } + ] + } + }, + { + "name": "latitude", + "type": "FLOAT", + "float_domain": { + "min": 100.0, + "max": 105.0 + }, + "presence": { + "minFraction": 1.0, + "minCount": "1" + }, + "shape": { + "dim": [ + { + "size": "1" + } + ] + } + }, + { + "name": "longitude", + "type": "FLOAT", + "presence": { + "minFraction": 1.0, + "minCount": "1" + }, + "float_domain": { + "min": 102.0, + "max": 105.0 + }, + "shape": { + "dim": [ + { + "size": "1" + } + ] + } + } + ], + "stringDomain": [ + { + "name": "location", + "value": [ + "(30.24258, -97.71726)", + "(30.24472, -97.72336)", + "(30.24891, -97.75019)" + ] + }, + { + "name": "name", + "value": [ + "10th & Red River", + "11th & Salina", + "11th & San Jacinto", + "13th & San Antonio", + "17th & Guadalupe" + ] + }, + { + "name": "status", + "value": [ + "active", + "closed" + ] + } + ] +} \ No newline at end of file diff --git a/sdk/python/tests/dataframes.py b/sdk/python/tests/dataframes.py index 28048322062..a50aa89810c 100644 --- a/sdk/python/tests/dataframes.py +++ b/sdk/python/tests/dataframes.py @@ -1,8 +1,8 @@ -import pandas as pd -import pytz -import numpy as np from datetime import datetime +import numpy as np +import pandas as pd +import pytz GOOD = pd.DataFrame( { diff --git a/sdk/python/tests/feast_core_server.py b/sdk/python/tests/feast_core_server.py index 7cc837a4f2d..3ac1b17d003 100644 --- a/sdk/python/tests/feast_core_server.py +++ b/sdk/python/tests/feast_core_server.py @@ -1,27 +1,22 @@ -from concurrent import futures -import time import logging +import time +from concurrent import futures + import grpc +from google.protobuf.timestamp_pb2 import Timestamp + import feast.core.CoreService_pb2_grpc as Core from feast.core.CoreService_pb2 import ( - GetFeastCoreVersionResponse, - ApplyFeatureSetResponse, ApplyFeatureSetRequest, - ListFeatureSetsResponse, + ApplyFeatureSetResponse, + GetFeastCoreVersionResponse, ListFeatureSetsRequest, -) -from google.protobuf.timestamp_pb2 import Timestamp -from feast.core.FeatureSet_pb2 import ( - FeatureSetSpec as FeatureSetSpec, - FeatureSetMeta, - FeatureSetStatus, -) -from feast.core.Source_pb2 import ( - SourceType as SourceTypeProto, - KafkaSourceConfig as KafkaSourceConfigProto, + ListFeatureSetsResponse, ) from feast.core.FeatureSet_pb2 import FeatureSet as FeatureSetProto -from typing import List +from feast.core.FeatureSet_pb2 import FeatureSetMeta, FeatureSetStatus +from feast.core.Source_pb2 import KafkaSourceConfig as KafkaSourceConfigProto +from feast.core.Source_pb2 import SourceType as SourceTypeProto _logger = logging.getLogger(__name__) @@ -45,21 +40,12 @@ def ListFeatureSets(self, request: ListFeatureSetsRequest, context): or request.filter.feature_set_name == "*" or fs.spec.name == request.filter.feature_set_name ) - and ( - not request.filter.feature_set_version - or str(fs.spec.version) == request.filter.feature_set_version - or request.filter.feature_set_version == "*" - ) ] return ListFeatureSetsResponse(feature_sets=filtered_feature_set_response) def ApplyFeatureSet(self, request: ApplyFeatureSetRequest, context): feature_set = request.feature_set - if feature_set.spec.version is None: - feature_set.spec.version = 1 - else: - feature_set.spec.version = feature_set.spec.version + 1 if feature_set.spec.source.type == SourceTypeProto.INVALID: feature_set.spec.source.kafka_source_config.CopyFrom( diff --git a/sdk/python/tests/feast_serving_server.py b/sdk/python/tests/feast_serving_server.py index eb46bde1215..983e74e8850 100644 --- a/sdk/python/tests/feast_serving_server.py +++ b/sdk/python/tests/feast_serving_server.py @@ -1,24 +1,23 @@ -from concurrent import futures -import time import logging +import time +from concurrent import futures +from typing import Dict import grpc +from google.protobuf.timestamp_pb2 import Timestamp + import feast.serving.ServingService_pb2_grpc as Serving +from feast.core import FeatureSet_pb2 as FeatureSetProto +from feast.core.CoreService_pb2 import ListFeatureSetsResponse +from feast.core.CoreService_pb2_grpc import CoreServiceStub from feast.serving.ServingService_pb2 import ( + GetFeastServingInfoResponse, GetOnlineFeaturesRequest, GetOnlineFeaturesResponse, - GetFeastServingInfoResponse, ) -from typing import Dict -from feast.core.CoreService_pb2_grpc import CoreServiceStub -from feast.core.CoreService_pb2 import ListFeatureSetsResponse -from feast.core import FeatureSet_pb2 as FeatureSetProto -from feast.types import ( - FeatureRow_pb2 as FeatureRowProto, - Field_pb2 as FieldProto, - Value_pb2 as ValueProto, -) -from google.protobuf.timestamp_pb2 import Timestamp +from feast.types import FeatureRow_pb2 as FeatureRowProto +from feast.types import Field_pb2 as FieldProto +from feast.types import Value_pb2 as ValueProto _ONE_DAY_IN_SECONDS = 60 * 60 * 24 @@ -68,7 +67,6 @@ def GetOnlineFeatures(self, request: GetOnlineFeaturesRequest, context): feature_data_sets=[ GetOnlineFeaturesResponse.FeatureDataSet( name="feature_set_1", - version="1", feature_rows=[ FeatureRowProto.FeatureRow( feature_set="feature_set_1", diff --git a/sdk/python/tests/test_client.py b/sdk/python/tests/test_client.py index 123cbe47fd6..380557ce928 100644 --- a/sdk/python/tests/test_client.py +++ b/sdk/python/tests/test_client.py @@ -11,62 +11,99 @@ # 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. -from datetime import datetime + +import pkgutil import tempfile +from concurrent import futures +from datetime import datetime +from unittest import mock + import grpc import pandas as pd +import pandavro +import pytest from google.protobuf.duration_pb2 import Duration from mock import MagicMock, patch from pytz import timezone -from pandavro import to_avro + +import dataframes import feast.core.CoreService_pb2_grpc as Core import feast.serving.ServingService_pb2_grpc as Serving -from feast.feature_set import FeatureSet -from feast.entity import Entity -from feast.feature_set import Feature -from feast.source import KafkaSource -from feast.core.FeatureSet_pb2 import ( - FeatureSetSpec as FeatureSetSpecProto, - FeatureSpec as FeatureSpecProto, - EntitySpec as EntitySpecProto, - FeatureSetMeta as FeatureSetMetaProto, - FeatureSetStatus as FeatureSetStatusProto, - FeatureSet as FeatureSetProto, -) -from feast.core.Source_pb2 import SourceType, KafkaSourceConfig, Source +from feast.client import Client from feast.core.CoreService_pb2 import ( GetFeastCoreVersionResponse, GetFeatureSetResponse, + ListIngestionJobsResponse, ) +from feast.core.FeatureSet_pb2 import EntitySpec as EntitySpecProto +from feast.core.FeatureSet_pb2 import FeatureSet as FeatureSetProto +from feast.core.FeatureSet_pb2 import FeatureSetMeta as FeatureSetMetaProto +from feast.core.FeatureSet_pb2 import FeatureSetSpec as FeatureSetSpecProto +from feast.core.FeatureSet_pb2 import FeatureSetStatus as FeatureSetStatusProto +from feast.core.FeatureSet_pb2 import FeatureSpec as FeatureSpecProto +from feast.core.IngestionJob_pb2 import IngestionJob as IngestJobProto +from feast.core.IngestionJob_pb2 import IngestionJobStatus +from feast.core.Source_pb2 import KafkaSourceConfig, Source, SourceType +from feast.core.Store_pb2 import Store +from feast.entity import Entity +from feast.feature_set import Feature, FeatureSet, FeatureSetRef +from feast.job import IngestJob from feast.serving.ServingService_pb2 import ( - GetFeastServingInfoResponse, - GetOnlineFeaturesResponse, - GetOnlineFeaturesRequest, - GetBatchFeaturesResponse, - Job as BatchFeaturesJob, - JobType, - JobStatus, DataFormat, - GetJobResponse, FeastServingType, + GetBatchFeaturesResponse, + GetFeastServingInfoResponse, + GetJobResponse, + GetOnlineFeaturesRequest, + GetOnlineFeaturesResponse, ) -import pytest -from feast.client import Client -from concurrent import futures -from feast_core_server import CoreServicer -from feast_serving_server import ServingServicer +from feast.serving.ServingService_pb2 import Job as BatchRetrievalJob +from feast.serving.ServingService_pb2 import JobStatus, JobType +from feast.source import KafkaSource from feast.types import Value_pb2 as ValueProto from feast.value_type import ValueType -from feast.job import Job -import dataframes +from feast_core_server import CoreServicer +from feast_serving_server import ServingServicer CORE_URL = "core.feast.example.com" SERVING_URL = "serving.example.com" +_PRIVATE_KEY_RESOURCE_PATH = "data/localhost.key" +_CERTIFICATE_CHAIN_RESOURCE_PATH = "data/localhost.pem" +_ROOT_CERTIFICATE_RESOURCE_PATH = "data/localhost.crt" class TestClient: - @pytest.fixture(scope="function") + @pytest.fixture + def secure_mock_client(self, mocker): + client = Client( + core_url=CORE_URL, + serving_url=SERVING_URL, + core_secure=True, + serving_secure=True, + ) + mocker.patch.object(client, "_connect_core") + mocker.patch.object(client, "_connect_serving") + client._core_url = CORE_URL + client._serving_url = SERVING_URL + return client + + @pytest.fixture + def mock_client(self, mocker): + client = Client(core_url=CORE_URL, serving_url=SERVING_URL) + mocker.patch.object(client, "_connect_core") + mocker.patch.object(client, "_connect_serving") + client._core_url = CORE_URL + client._serving_url = SERVING_URL + return client + + @pytest.fixture + def server_credentials(self): + private_key = pkgutil.get_data(__name__, _PRIVATE_KEY_RESOURCE_PATH) + certificate_chain = pkgutil.get_data(__name__, _CERTIFICATE_CHAIN_RESOURCE_PATH) + return grpc.ssl_server_credentials(((private_key, certificate_chain),)) + + @pytest.fixture def core_server(self): server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) Core.add_CoreServiceServicer_to_server(CoreServicer(), server) @@ -75,7 +112,7 @@ def core_server(self): yield server server.stop(0) - @pytest.fixture(scope="function") + @pytest.fixture def serving_server(self): server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) Serving.add_ServingServiceServicer_to_server(ServingServicer(), server) @@ -85,37 +122,73 @@ def serving_server(self): server.stop(0) @pytest.fixture - def mock_client(self, mocker): - client = Client(core_url=CORE_URL, serving_url=SERVING_URL) - mocker.patch.object(client, "_connect_core") - mocker.patch.object(client, "_connect_serving") - client._core_url = CORE_URL - client._serving_url = SERVING_URL - return client + def secure_core_server(self, server_credentials): + server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) + Core.add_CoreServiceServicer_to_server(CoreServicer(), server) + server.add_secure_port("[::]:50053", server_credentials) + server.start() + yield server + server.stop(0) + + @pytest.fixture + def secure_serving_server(self, server_credentials): + server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) + Serving.add_ServingServiceServicer_to_server(ServingServicer(), server) + + server.add_secure_port("[::]:50054", server_credentials) + server.start() + yield server + server.stop(0) + + @pytest.fixture + def secure_client(self, secure_core_server, secure_serving_server): + root_certificate_credentials = pkgutil.get_data( + __name__, _ROOT_CERTIFICATE_RESOURCE_PATH + ) + # this is needed to establish a secure connection using self-signed certificates, for the purpose of the test + ssl_channel_credentials = grpc.ssl_channel_credentials( + root_certificates=root_certificate_credentials + ) + with mock.patch( + "grpc.ssl_channel_credentials", + MagicMock(return_value=ssl_channel_credentials), + ): + yield Client( + core_url="localhost:50053", + serving_url="localhost:50054", + core_secure=True, + serving_secure=True, + ) @pytest.fixture def client(self, core_server, serving_server): return Client(core_url="localhost:50051", serving_url="localhost:50052") - def test_version(self, mock_client, mocker): - mock_client._core_service_stub = Core.CoreServiceStub(grpc.insecure_channel("")) - mock_client._serving_service_stub = Serving.ServingServiceStub( + @pytest.mark.parametrize( + "mocked_client", + [pytest.lazy_fixture("mock_client"), pytest.lazy_fixture("secure_mock_client")], + ) + def test_version(self, mocked_client, mocker): + mocked_client._core_service_stub = Core.CoreServiceStub( + grpc.insecure_channel("") + ) + mocked_client._serving_service_stub = Serving.ServingServiceStub( grpc.insecure_channel("") ) mocker.patch.object( - mock_client._core_service_stub, + mocked_client._core_service_stub, "GetFeastCoreVersion", return_value=GetFeastCoreVersionResponse(version="0.3.2"), ) mocker.patch.object( - mock_client._serving_service_stub, + mocked_client._serving_service_stub, "GetFeastServingInfo", return_value=GetFeastServingInfoResponse(version="0.3.2"), ) - status = mock_client.version() + status = mocked_client.version() assert ( status["core"]["url"] == CORE_URL and status["core"]["version"] == "0.3.2" @@ -123,19 +196,28 @@ def test_version(self, mock_client, mocker): and status["serving"]["version"] == "0.3.2" ) - def test_get_online_features(self, mock_client, mocker): + @pytest.mark.parametrize( + "mocked_client", + [pytest.lazy_fixture("mock_client"), pytest.lazy_fixture("secure_mock_client")], + ) + def test_get_online_features(self, mocked_client, mocker): ROW_COUNT = 300 - mock_client._serving_service_stub = Serving.ServingServiceStub( + mocked_client._serving_service_stub = Serving.ServingServiceStub( grpc.insecure_channel("") ) - fields = dict() - for feature_num in range(1, 10): - fields[f"my_project/feature_{str(feature_num)}:1"] = ValueProto.Value( - int64_val=feature_num - ) - field_values = GetOnlineFeaturesResponse.FieldValues(fields=fields) + def int_val(x): + return ValueProto.Value(int64_val=x) + + # serving can return feature references with projects, + # get_online_features() should strip the project part. + field_values = GetOnlineFeaturesResponse.FieldValues( + fields={ + "driver_project/driver:driver_id": int_val(1), + "driver_project/driver_id": int_val(9), + } + ) response = GetOnlineFeaturesResponse() entity_rows = [] @@ -143,49 +225,47 @@ def test_get_online_features(self, mock_client, mocker): response.field_values.append(field_values) entity_rows.append( GetOnlineFeaturesRequest.EntityRow( - fields={"customer_id": ValueProto.Value(int64_val=row_number)} + fields={"customer_id": int_val(row_number)} ) ) mocker.patch.object( - mock_client._serving_service_stub, + mocked_client._serving_service_stub, "GetOnlineFeatures", return_value=response, ) - response = mock_client.get_online_features( + # NOTE: Feast Serving does not allow for feature references + # that specify the same feature in the same request + response = mocked_client.get_online_features( entity_rows=entity_rows, - feature_refs=[ - "my_project/feature_1:1", - "my_project/feature_2:1", - "my_project/feature_3:1", - "my_project/feature_4:1", - "my_project/feature_5:1", - "my_project/feature_6:1", - "my_project/feature_7:1", - "my_project/feature_8:1", - "my_project/feature_9:1", - ], + feature_refs=["driver:driver_id", "driver_id"], + project="driver_project", ) # type: GetOnlineFeaturesResponse assert ( - response.field_values[0].fields["my_project/feature_1:1"].int64_val == 1 - and response.field_values[0].fields["my_project/feature_9:1"].int64_val == 9 + response.field_values[0].fields["driver:driver_id"].int64_val == 1 + and response.field_values[0].fields["driver_id"].int64_val == 9 ) - def test_get_feature_set(self, mock_client, mocker): - mock_client._core_service_stub = Core.CoreServiceStub(grpc.insecure_channel("")) + @pytest.mark.parametrize( + "mocked_client", + [pytest.lazy_fixture("mock_client"), pytest.lazy_fixture("secure_mock_client")], + ) + def test_get_feature_set(self, mocked_client, mocker): + mocked_client._core_service_stub = Core.CoreServiceStub( + grpc.insecure_channel("") + ) from google.protobuf.duration_pb2 import Duration mocker.patch.object( - mock_client._core_service_stub, + mocked_client._core_service_stub, "GetFeatureSet", return_value=GetFeatureSetResponse( feature_set=FeatureSetProto( spec=FeatureSetSpecProto( name="my_feature_set", - version=2, max_age=Duration(seconds=3600), features=[ FeatureSpecProto( @@ -214,12 +294,11 @@ def test_get_feature_set(self, mock_client, mocker): ) ), ) - mock_client.set_project("my_project") - feature_set = mock_client.get_feature_set("my_feature_set", version=2) + mocked_client.set_project("my_project") + feature_set = mocked_client.get_feature_set("my_feature_set") assert ( feature_set.name == "my_feature_set" - and feature_set.version == 2 and feature_set.fields["my_feature_1"].name == "my_feature_1" and feature_set.fields["my_feature_1"].dtype == ValueType.FLOAT and feature_set.fields["my_entity_1"].name == "my_entity_1" @@ -228,25 +307,132 @@ def test_get_feature_set(self, mock_client, mocker): and len(feature_set.entities) == 1 ) - def test_get_batch_features(self, mock_client, mocker): + @pytest.mark.parametrize( + "mocked_client", + [pytest.lazy_fixture("mock_client"), pytest.lazy_fixture("secure_mock_client")], + ) + def test_list_ingest_jobs(self, mocked_client, mocker): + mocker.patch.object( + mocked_client, + "_core_service_stub", + return_value=Core.CoreServiceStub(grpc.insecure_channel("")), + ) + + feature_set_proto = FeatureSetProto( + spec=FeatureSetSpecProto( + project="test", name="driver", max_age=Duration(seconds=3600), + ) + ) + + mocker.patch.object( + mocked_client._core_service_stub, + "ListIngestionJobs", + return_value=ListIngestionJobsResponse( + jobs=[ + IngestJobProto( + id="kafka-to-redis", + external_id="job-2222", + status=IngestionJobStatus.RUNNING, + feature_sets=[feature_set_proto], + source=Source( + type=SourceType.KAFKA, + kafka_source_config=KafkaSourceConfig( + bootstrap_servers="localhost:9092", topic="topic" + ), + ), + store=Store(name="redis"), + ) + ] + ), + ) + + # list ingestion jobs by target feature set reference + ingest_jobs = mocked_client.list_ingest_jobs( + feature_set_ref=FeatureSetRef.from_feature_set( + FeatureSet.from_proto(feature_set_proto) + ) + ) + assert len(ingest_jobs) >= 1 + + ingest_job = ingest_jobs[0] + assert ( + ingest_job.status == IngestionJobStatus.RUNNING + and ingest_job.id == "kafka-to-redis" + and ingest_job.external_id == "job-2222" + and ingest_job.feature_sets[0].name == "driver" + and ingest_job.source.source_type == "Kafka" + ) + + @pytest.mark.parametrize( + "mocked_client", + [pytest.lazy_fixture("mock_client"), pytest.lazy_fixture("secure_mock_client")], + ) + def test_restart_ingest_job(self, mocked_client, mocker): + mocker.patch.object( + mocked_client, + "_core_service_stub", + return_value=Core.CoreServiceStub(grpc.insecure_channel("")), + ) + + ingest_job = IngestJob( + job_proto=IngestJobProto( + id="kafka-to-redis", + external_id="job#2222", + status=IngestionJobStatus.ERROR, + ), + core_stub=mocked_client._core_service_stub, + ) + + mocked_client.restart_ingest_job(ingest_job) + assert mocked_client._core_service_stub.RestartIngestionJob.called + + @pytest.mark.parametrize( + "mocked_client", + [pytest.lazy_fixture("mock_client"), pytest.lazy_fixture("secure_mock_client")], + ) + def test_stop_ingest_job(self, mocked_client, mocker): + mocker.patch.object( + mocked_client, + "_core_service_stub", + return_value=Core.CoreServiceStub(grpc.insecure_channel("")), + ) + + ingest_job = IngestJob( + job_proto=IngestJobProto( + id="kafka-to-redis", + external_id="job#2222", + status=IngestionJobStatus.RUNNING, + ), + core_stub=mocked_client._core_service_stub, + ) + + mocked_client.stop_ingest_job(ingest_job) + assert mocked_client._core_service_stub.StopIngestionJob.called + + @pytest.mark.parametrize( + "mocked_client", + [pytest.lazy_fixture("mock_client"), pytest.lazy_fixture("secure_mock_client")], + ) + def test_get_batch_features(self, mocked_client, mocker): - mock_client._serving_service_stub = Serving.ServingServiceStub( + mocked_client._serving_service_stub = Serving.ServingServiceStub( + grpc.insecure_channel("") + ) + mocked_client._core_service_stub = Core.CoreServiceStub( grpc.insecure_channel("") ) - mock_client._core_service_stub = Core.CoreServiceStub(grpc.insecure_channel("")) mocker.patch.object( - mock_client._core_service_stub, + mocked_client._core_service_stub, "GetFeatureSet", return_value=GetFeatureSetResponse( feature_set=FeatureSetProto( spec=FeatureSetSpecProto( - name="customer_fs", - version=1, - project="my_project", + name="driver", + project="driver_project", entities=[ EntitySpecProto( - name="customer", value_type=ValueProto.ValueType.INT64 + name="driver", value_type=ValueProto.ValueType.INT64 ), EntitySpecProto( name="transaction", @@ -255,11 +441,10 @@ def test_get_batch_features(self, mock_client, mocker): ], features=[ FeatureSpecProto( - name="customer_feature_1", - value_type=ValueProto.ValueType.FLOAT, + name="driver_id", value_type=ValueProto.ValueType.FLOAT, ), FeatureSpecProto( - name="customer_feature_2", + name="driver_name", value_type=ValueProto.ValueType.STRING, ), ], @@ -272,21 +457,20 @@ def test_get_batch_features(self, mock_client, mocker): expected_dataframe = pd.DataFrame( { "datetime": [datetime.utcnow() for _ in range(3)], - "customer": [1001, 1002, 1003], + "driver": [1001, 1002, 1003], "transaction": [1001, 1002, 1003], - "my_project/customer_feature_1:1": [1001, 1002, 1003], - "my_project/customer_feature_2:1": [1001, 1002, 1003], + "driver_id": [1001, 1002, 1003], } ) final_results = tempfile.mktemp() - to_avro(file_path_or_buffer=final_results, df=expected_dataframe) + pandavro.to_avro(file_path_or_buffer=final_results, df=expected_dataframe) mocker.patch.object( - mock_client._serving_service_stub, + mocked_client._serving_service_stub, "GetBatchFeatures", return_value=GetBatchFeaturesResponse( - job=BatchFeaturesJob( + job=BatchRetrievalJob( id="123", type=JobType.JOB_TYPE_DOWNLOAD, status=JobStatus.JOB_STATUS_DONE, @@ -297,10 +481,10 @@ def test_get_batch_features(self, mock_client, mocker): ) mocker.patch.object( - mock_client._serving_service_stub, + mocked_client._serving_service_stub, "GetJob", return_value=GetJobResponse( - job=BatchFeaturesJob( + job=BatchRetrievalJob( id="123", type=JobType.JOB_TYPE_DOWNLOAD, status=JobStatus.JOB_STATUS_DONE, @@ -311,7 +495,7 @@ def test_get_batch_features(self, mock_client, mocker): ) mocker.patch.object( - mock_client._serving_service_stub, + mocked_client._serving_service_stub, "GetFeastServingInfo", return_value=GetFeastServingInfoResponse( job_staging_location=f"file://{tempfile.mkdtemp()}/", @@ -319,38 +503,39 @@ def test_get_batch_features(self, mock_client, mocker): ), ) - mock_client.set_project("project1") - response = mock_client.get_batch_features( - entity_rows=pd.DataFrame( - { - "datetime": [ - pd.datetime.now(tz=timezone("Asia/Singapore")) for _ in range(3) - ], - "customer": [1001, 1002, 1003], - "transaction": [1001, 1002, 1003], - } - ), - feature_refs=[ - "my_project/customer_feature_1:1", - "my_project/customer_feature_2:1", - ], - ) # type: Job + mocked_client.set_project("project1") + # TODO: Abstract away GCS client and GCP dependency + # NOTE: Feast Serving does not allow for feature references + # that specify the same feature in the same request. + with patch("google.cloud.storage.Client"): + response = mocked_client.get_batch_features( + entity_rows=pd.DataFrame( + { + "datetime": [ + pd.datetime.now(tz=timezone("Asia/Singapore")) + for _ in range(3) + ], + "driver": [1001, 1002, 1003], + "transaction": [1001, 1002, 1003], + } + ), + feature_refs=["driver:driver_id", "driver_id"], + project="driver_project", + ) # Type: GetBatchFeaturesResponse assert response.id == "123" and response.status == JobStatus.JOB_STATUS_DONE actual_dataframe = response.to_dataframe() - assert actual_dataframe[ - ["my_project/customer_feature_1:1", "my_project/customer_feature_2:1"] - ].equals( - expected_dataframe[ - ["my_project/customer_feature_1:1", "my_project/customer_feature_2:1"] - ] - ) + assert actual_dataframe[["driver_id"]].equals(expected_dataframe[["driver_id"]]) - def test_apply_feature_set_success(self, client): + @pytest.mark.parametrize( + "test_client", + [pytest.lazy_fixture("client"), pytest.lazy_fixture("secure_client")], + ) + def test_apply_feature_set_success(self, test_client): - client.set_project("project1") + test_client.set_project("project1") # Create Feature Sets fs1 = FeatureSet("my-feature-set-1") @@ -364,10 +549,10 @@ def test_apply_feature_set_success(self, client): fs2.add(Entity(name="fs2-my-entity-1", dtype=ValueType.INT64)) # Register Feature Set with Core - client.apply(fs1) - client.apply(fs2) + test_client.apply(fs1) + test_client.apply(fs2) - feature_sets = client.list_feature_sets() + feature_sets = test_client.list_feature_sets() # List Feature Sets assert ( @@ -375,12 +560,27 @@ def test_apply_feature_set_success(self, client): and feature_sets[0].name == "my-feature-set-1" and feature_sets[0].features[0].name == "fs1-my-feature-1" and feature_sets[0].features[0].dtype == ValueType.INT64 + and feature_sets[0].features[1].name == "fs1-my-feature-2" + and feature_sets[0].features[1].dtype == ValueType.STRING + and feature_sets[0].entities[0].name == "fs1-my-entity-1" + and feature_sets[0].entities[0].dtype == ValueType.INT64 + and feature_sets[1].features[0].name == "fs2-my-feature-1" + and feature_sets[1].features[0].dtype == ValueType.STRING_LIST + and feature_sets[1].features[1].name == "fs2-my-feature-2" and feature_sets[1].features[1].dtype == ValueType.BYTES_LIST + and feature_sets[1].entities[0].name == "fs2-my-entity-1" + and feature_sets[1].entities[0].dtype == ValueType.INT64 ) - @pytest.mark.parametrize("dataframe", [dataframes.GOOD]) - def test_feature_set_ingest_success(self, dataframe, client, mocker): - client.set_project("project1") + @pytest.mark.parametrize( + "dataframe,test_client", + [ + (dataframes.GOOD, pytest.lazy_fixture("client")), + (dataframes.GOOD, pytest.lazy_fixture("secure_client")), + ], + ) + def test_feature_set_ingest_success(self, dataframe, test_client, mocker): + test_client.set_project("project1") driver_fs = FeatureSet( "driver-feature-set", source=KafkaSource(brokers="kafka:9092", topic="test") ) @@ -390,27 +590,65 @@ def test_feature_set_ingest_success(self, dataframe, client, mocker): driver_fs.add(Entity(name="entity_id", dtype=ValueType.INT64)) # Register with Feast core - client.apply(driver_fs) + test_client.apply(driver_fs) driver_fs = driver_fs.to_proto() driver_fs.meta.status = FeatureSetStatusProto.STATUS_READY mocker.patch.object( - client._core_service_stub, + test_client._core_service_stub, "GetFeatureSet", return_value=GetFeatureSetResponse(feature_set=driver_fs), ) # Need to create a mock producer - with patch("feast.client.get_producer") as mocked_queue: + with patch("feast.client.get_producer"): # Ingest data into Feast - client.ingest("driver-feature-set", dataframe) + test_client.ingest("driver-feature-set", dataframe) + + @pytest.mark.parametrize( + "dataframe,test_client,exception", + [(dataframes.GOOD, pytest.lazy_fixture("client"), Exception)], + ) + def test_feature_set_ingest_throws_exception_if_kafka_down( + self, dataframe, test_client, exception, mocker + ): + + test_client.set_project("project1") + driver_fs = FeatureSet( + "driver-feature-set", + source=KafkaSource(brokers="localhost:4412", topic="test"), + ) + driver_fs.add(Feature(name="feature_1", dtype=ValueType.FLOAT)) + driver_fs.add(Feature(name="feature_2", dtype=ValueType.STRING)) + driver_fs.add(Feature(name="feature_3", dtype=ValueType.INT64)) + driver_fs.add(Entity(name="entity_id", dtype=ValueType.INT64)) - @pytest.mark.parametrize("dataframe,exception", [(dataframes.GOOD, TimeoutError)]) + # Register with Feast core + test_client.apply(driver_fs) + driver_fs = driver_fs.to_proto() + driver_fs.meta.status = FeatureSetStatusProto.STATUS_READY + + mocker.patch.object( + test_client._core_service_stub, + "GetFeatureSet", + return_value=GetFeatureSetResponse(feature_set=driver_fs), + ) + + with pytest.raises(exception): + test_client.ingest("driver-feature-set", dataframe) + + @pytest.mark.parametrize( + "dataframe,exception,test_client", + [ + (dataframes.GOOD, TimeoutError, pytest.lazy_fixture("client")), + (dataframes.GOOD, TimeoutError, pytest.lazy_fixture("secure_client")), + ], + ) def test_feature_set_ingest_fail_if_pending( - self, dataframe, exception, client, mocker + self, dataframe, exception, test_client, mocker ): with pytest.raises(exception): - client.set_project("project1") + test_client.set_project("project1") driver_fs = FeatureSet( "driver-feature-set", source=KafkaSource(brokers="kafka:9092", topic="test"), @@ -421,31 +659,47 @@ def test_feature_set_ingest_fail_if_pending( driver_fs.add(Entity(name="entity_id", dtype=ValueType.INT64)) # Register with Feast core - client.apply(driver_fs) + test_client.apply(driver_fs) driver_fs = driver_fs.to_proto() driver_fs.meta.status = FeatureSetStatusProto.STATUS_PENDING mocker.patch.object( - client._core_service_stub, + test_client._core_service_stub, "GetFeatureSet", return_value=GetFeatureSetResponse(feature_set=driver_fs), ) # Need to create a mock producer - with patch("feast.client.get_producer") as mocked_queue: + with patch("feast.client.get_producer"): # Ingest data into Feast - client.ingest("driver-feature-set", dataframe, timeout=1) + test_client.ingest("driver-feature-set", dataframe, timeout=1) @pytest.mark.parametrize( - "dataframe,exception", + "dataframe,exception,test_client", [ - (dataframes.BAD_NO_DATETIME, Exception), - (dataframes.BAD_INCORRECT_DATETIME_TYPE, Exception), - (dataframes.BAD_NO_ENTITY, Exception), - (dataframes.NO_FEATURES, Exception), + (dataframes.BAD_NO_DATETIME, Exception, pytest.lazy_fixture("client")), + ( + dataframes.BAD_INCORRECT_DATETIME_TYPE, + Exception, + pytest.lazy_fixture("client"), + ), + (dataframes.BAD_NO_ENTITY, Exception, pytest.lazy_fixture("client")), + (dataframes.NO_FEATURES, Exception, pytest.lazy_fixture("client")), + ( + dataframes.BAD_NO_DATETIME, + Exception, + pytest.lazy_fixture("secure_client"), + ), + ( + dataframes.BAD_INCORRECT_DATETIME_TYPE, + Exception, + pytest.lazy_fixture("secure_client"), + ), + (dataframes.BAD_NO_ENTITY, Exception, pytest.lazy_fixture("secure_client")), + (dataframes.NO_FEATURES, Exception, pytest.lazy_fixture("secure_client")), ], ) - def test_feature_set_ingest_failure(self, client, dataframe, exception): + def test_feature_set_ingest_failure(self, test_client, dataframe, exception): with pytest.raises(exception): # Create feature set driver_fs = FeatureSet("driver-feature-set") @@ -454,15 +708,21 @@ def test_feature_set_ingest_failure(self, client, dataframe, exception): driver_fs.infer_fields_from_df(dataframe) # Register with Feast core - client.apply(driver_fs) + test_client.apply(driver_fs) # Ingest data into Feast - client.ingest(driver_fs, dataframe=dataframe) + test_client.ingest(driver_fs, dataframe=dataframe) - @pytest.mark.parametrize("dataframe", [dataframes.ALL_TYPES]) - def test_feature_set_types_success(self, client, dataframe, mocker): + @pytest.mark.parametrize( + "dataframe,test_client", + [ + (dataframes.ALL_TYPES, pytest.lazy_fixture("client")), + (dataframes.ALL_TYPES, pytest.lazy_fixture("secure_client")), + ], + ) + def test_feature_set_types_success(self, test_client, dataframe, mocker): - client.set_project("project1") + test_client.set_project("project1") all_types_fs = FeatureSet( name="all_types", @@ -482,22 +742,62 @@ def test_feature_set_types_success(self, client, dataframe, mocker): Feature(name="bytes_list_feature", dtype=ValueType.BYTES_LIST), # Feature(name="bool_list_feature", # dtype=ValueType.BOOL_LIST), # TODO: Add support for this - # type again https://github.com/gojek/feast/issues/341 + # type again https://github.com/feast-dev/feast/issues/341 Feature(name="double_list_feature", dtype=ValueType.DOUBLE_LIST), ], max_age=Duration(seconds=3600), ) # Register with Feast core - client.apply(all_types_fs) + test_client.apply(all_types_fs) mocker.patch.object( - client._core_service_stub, + test_client._core_service_stub, "GetFeatureSet", return_value=GetFeatureSetResponse(feature_set=all_types_fs.to_proto()), ) # Need to create a mock producer - with patch("feast.client.get_producer") as mocked_queue: + with patch("feast.client.get_producer"): # Ingest data into Feast - client.ingest(all_types_fs, dataframe) + test_client.ingest(all_types_fs, dataframe) + + @patch("grpc.channel_ready_future") + def test_secure_channel_creation_with_secure_client(self, _mocked_obj): + client = Client( + core_url="localhost:50051", + serving_url="localhost:50052", + serving_secure=True, + core_secure=True, + ) + with mock.patch("grpc.secure_channel") as _grpc_mock, mock.patch( + "grpc.ssl_channel_credentials", MagicMock(return_value="test") + ) as _mocked_credentials: + client._connect_serving() + _grpc_mock.assert_called_with( + client.serving_url, _mocked_credentials.return_value + ) + + @mock.patch("grpc.channel_ready_future") + def test_secure_channel_creation_with_secure_serving_url( + self, _mocked_obj, + ): + client = Client(core_url="localhost:50051", serving_url="localhost:443") + with mock.patch("grpc.secure_channel") as _grpc_mock, mock.patch( + "grpc.ssl_channel_credentials", MagicMock(return_value="test") + ) as _mocked_credentials: + client._connect_serving() + _grpc_mock.assert_called_with( + client.serving_url, _mocked_credentials.return_value + ) + + @patch("grpc.channel_ready_future") + def test_secure_channel_creation_with_secure_core_url(self, _mocked_obj): + client = Client(core_url="localhost:443", serving_url="localhost:50054") + with mock.patch("grpc.secure_channel") as _grpc_mock, mock.patch( + "grpc.ssl_channel_credentials", MagicMock(return_value="test") + ) as _mocked_credentials: + client._connect_core() + _grpc_mock.assert_called_with( + client.core_url, _mocked_credentials.return_value + ) diff --git a/sdk/python/tests/test_config.py b/sdk/python/tests/test_config.py new file mode 100644 index 00000000000..9ed34a736a2 --- /dev/null +++ b/sdk/python/tests/test_config.py @@ -0,0 +1,139 @@ +# Copyright 2020 The Feast Authors +# +# 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. + +import os +from tempfile import mkstemp + +import pytest + +from feast.config import Config + + +class TestConfig: + @pytest.fixture + def normal_config(self): + fd, path = mkstemp() + return Config(path=path) + + def test_init_config_file_with_path(self): + configuration_string = "[general]\nCORE_URL = grpc://127.0.0.1:6565" + + fd, path = mkstemp() + with open(fd, "w") as f: + f.write(configuration_string) + config = Config(path=path) + assert config.get("core_url") == "grpc://127.0.0.1:6565" + + def test_load_environmental_variable(self, normal_config): + import os + + serving_url = "http://196.25.1.1" + os.environ["FEAST_SERVING_URL"] = serving_url + assert normal_config.get("SERVING_URL") == serving_url + del os.environ["FEAST_SERVING_URL"] + + def test_env_var_not_case_sensitive(self, normal_config): + import os + + serving_url = "http://196.25.1.1" + os.environ["FEAST_SerVING_url"] = serving_url + assert normal_config.get("SERVING_URL") == serving_url + + def test_force_options(self): + fd, path = mkstemp() + options = {"feast_config_1": "one", "random_config_two": 2} + config = Config(options, path) + assert config.get("feast_config_1") == "one" + + def test_init_options_precedence(self): + """ + Init options > env var > file options > default options + """ + fd, path = mkstemp() + os.environ["FEAST_CORE_URL"] = "env" + options = {"core_url": "init", "serving_url": "init"} + configuration_string = "[general]\nCORE_URL = file\n" + with open(fd, "w") as f: + f.write(configuration_string) + config = Config(options, path) + assert config.get("core_url") == "init" + del os.environ["FEAST_CORE_URL"] + + def test_env_var_precedence(self): + """ + Env vars > file options > default options + """ + fd, path = mkstemp() + os.environ["FEAST_CORE_URL"] = "env" + configuration_string = "[general]\nCORE_URL = file\n" + with open(fd, "w") as f: + f.write(configuration_string) + config = Config(path=path) + assert config.get("CORE_URL") == "env" + + del os.environ["FEAST_CORE_URL"] + + def test_file_option_precedence(self): + """ + file options > default options + """ + fd, path = mkstemp() + configuration_string = "[general]\nCORE_URL = file\n" + with open(fd, "w") as f: + f.write(configuration_string) + config = Config(path=path) + assert config.get("CORE_URL") == "file" + + def test_default_options(self): + """ + default options + """ + fd, path = mkstemp() + config = Config(path=path) + assert config.get("CORE_URL") == "localhost:6565" + + def test_type_casting(self): + """ + Test type casting of strings to other types + """ + fd, path = mkstemp() + os.environ["FEAST_INT_VAR"] = "1" + os.environ["FEAST_FLOAT_VAR"] = "1.0" + os.environ["FEAST_BOOLEAN_VAR"] = "True" + config = Config(path=path) + + assert config.getint("INT_VAR") == 1 + assert config.getfloat("FLOAT_VAR") == 1.0 + assert config.getboolean("BOOLEAN_VAR") is True + + def test_set_value(self): + """ + Test type casting of strings to other types + """ + fd, path = mkstemp() + config = Config(path=path) + config.set("my_val", 1) + + assert config.getint("my_val") == 1 + + def test_exists(self): + """ + Test type casting of strings to other types + """ + fd, path = mkstemp() + config = Config(path=path) + config.set("my_val_exist", 1) + + assert config.exists("my_val_exist") is True + assert config.exists("my_val_not_exist") is False diff --git a/sdk/python/tests/test_feature_set.py b/sdk/python/tests/test_feature_set.py index 57d7a8f8100..04e75c9e76d 100644 --- a/sdk/python/tests/test_feature_set.py +++ b/sdk/python/tests/test_feature_set.py @@ -11,21 +11,29 @@ # 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. +import pathlib +from concurrent import futures from datetime import datetime +import grpc +import pandas as pd +import pytest import pytz +from google.protobuf import json_format +import dataframes +import feast.core.CoreService_pb2_grpc as Core +from feast.client import Client from feast.entity import Entity -from feast.feature_set import FeatureSet, Feature +from feast.feature_set import ( + Feature, + FeatureSet, + FeatureSetRef, + _make_tfx_schema_domain_info_inline, +) from feast.value_type import ValueType -from feast.client import Client -import pandas as pd -import pytest -from concurrent import futures -import grpc from feast_core_server import CoreServicer -import feast.core.CoreService_pb2_grpc as Core -import dataframes +from tensorflow_metadata.proto.v0 import schema_pb2 CORE_URL = "core.feast.local" SERVING_URL = "serving.feast.local" @@ -167,3 +175,106 @@ def test_add_features_from_df_success( ) assert len(my_feature_set.features) == feature_count assert len(my_feature_set.entities) == entity_count + + def test_import_tfx_schema(self): + tests_folder = pathlib.Path(__file__).parent + test_input_schema_json = open( + tests_folder / "data" / "tensorflow_metadata" / "bikeshare_schema.json" + ).read() + test_input_schema = schema_pb2.Schema() + json_format.Parse(test_input_schema_json, test_input_schema) + + feature_set = FeatureSet( + name="bikeshare", + entities=[Entity(name="station_id", dtype=ValueType.INT64)], + features=[ + Feature(name="name", dtype=ValueType.STRING), + Feature(name="status", dtype=ValueType.STRING), + Feature(name="latitude", dtype=ValueType.FLOAT), + Feature(name="longitude", dtype=ValueType.FLOAT), + Feature(name="location", dtype=ValueType.STRING), + ], + ) + + # Before update + for entity in feature_set.entities: + assert entity.presence is None + assert entity.shape is None + for feature in feature_set.features: + assert feature.presence is None + assert feature.shape is None + assert feature.string_domain is None + assert feature.float_domain is None + assert feature.int_domain is None + + feature_set.import_tfx_schema(test_input_schema) + + # After update + for feature in feature_set.features: + assert feature.presence is not None + assert feature.shape is not None + if feature.name in ["location", "name", "status"]: + assert feature.string_domain is not None + elif feature.name in ["latitude", "longitude"]: + assert feature.float_domain is not None + elif feature.name in ["station_id"]: + assert feature.int_domain is not None + + def test_export_tfx_schema(self): + tests_folder = pathlib.Path(__file__).parent + test_input_feature_set = FeatureSet.from_yaml( + str( + tests_folder + / "data" + / "tensorflow_metadata" + / "bikeshare_feature_set.yaml" + ) + ) + + expected_schema_json = open( + tests_folder / "data" / "tensorflow_metadata" / "bikeshare_schema.json" + ).read() + expected_schema = schema_pb2.Schema() + json_format.Parse(expected_schema_json, expected_schema) + _make_tfx_schema_domain_info_inline(expected_schema) + + actual_schema = test_input_feature_set.export_tfx_schema() + + assert len(actual_schema.feature) == len(expected_schema.feature) + for actual, expected in zip(actual_schema.feature, expected_schema.feature): + assert actual.SerializeToString() == expected.SerializeToString() + + +def make_tfx_schema_domain_info_inline(schema): + # Copy top-level domain info defined in the schema to inline definition. + # One use case is in FeatureSet which does not have access to the top-level domain + # info. + domain_ref_to_string_domain = {d.name: d for d in schema.string_domain} + domain_ref_to_float_domain = {d.name: d for d in schema.float_domain} + domain_ref_to_int_domain = {d.name: d for d in schema.int_domain} + + for feature in schema.feature: + domain_info_case = feature.WhichOneof("domain_info") + if domain_info_case == "domain": + domain_ref = feature.domain + if domain_ref in domain_ref_to_string_domain: + feature.string_domain.MergeFrom(domain_ref_to_string_domain[domain_ref]) + elif domain_ref in domain_ref_to_float_domain: + feature.float_domain.MergeFrom(domain_ref_to_float_domain[domain_ref]) + elif domain_ref in domain_ref_to_int_domain: + feature.int_domain.MergeFrom(domain_ref_to_int_domain[domain_ref]) + + +class TestFeatureSetRef: + def test_from_feature_set(self): + feature_set = FeatureSet("test", "test") + ref = FeatureSetRef.from_feature_set(feature_set) + + assert ref.name == "test" + assert ref.project == "test" + + def test_str_ref(self): + original_ref = FeatureSetRef(project="test", name="test") + ref_str = repr(original_ref) + parsed_ref = FeatureSetRef.from_str(ref_str) + assert original_ref == parsed_ref diff --git a/serving/README.md b/serving/README.md index f88f30923b2..39eef311033 100644 --- a/serving/README.md +++ b/serving/README.md @@ -28,7 +28,6 @@ grpc_cli call localhost:6566 GetFeastServingType '' grpc_cli call localhost:6565 ApplyFeatureSet ' feature_set { name: "driver" - version: 1 entities { name: "driver_id" value_type: STRING @@ -53,14 +52,12 @@ feature_set { grpc_cli call localhost:6565 GetFeatureSets ' filter { feature_set_name: "driver" - feature_set_version: "1" } ' grpc_cli call localhost:6566 GetBatchFeatures ' feature_sets { name: "driver" - version: 1 feature_names: "booking_completed_count" max_age { seconds: 86400 diff --git a/serving/lombok.config b/serving/lombok.config deleted file mode 100644 index 8f7e8aa1ac9..00000000000 --- a/serving/lombok.config +++ /dev/null @@ -1 +0,0 @@ -lombok.addLombokGeneratedAnnotation = true \ No newline at end of file diff --git a/serving/pom.xml b/serving/pom.xml index dc3391df62f..3754777da07 100644 --- a/serving/pom.xml +++ b/serving/pom.xml @@ -34,22 +34,31 @@ spring-plugins Spring Plugins - http://repo.spring.io/plugins-release + https://repo.spring.io/plugins-release + + org.jacoco + jacoco-maven-plugin + + org.springframework.boot spring-boot-maven-plugin false - - - org.xolstice.maven.plugins - protobuf-maven-plugin + + + build-info + + build-info + + + org.apache.maven.plugins @@ -74,6 +83,37 @@ + + dev.feast + datatypes-java + ${project.version} + + + + + dev.feast + feast-storage-api + ${project.version} + + + + dev.feast + feast-storage-connector-redis + ${project.version} + + + + dev.feast + feast-storage-connector-redis-cluster + ${project.version} + + + + dev.feast + feast-storage-connector-bigquery + ${project.version} + + org.slf4j @@ -96,6 +136,10 @@ org.springframework.boot spring-boot-starter-log4j2 + + org.apache.logging.log4j + log4j-web + org.springframework.boot @@ -108,6 +152,7 @@ io.github.lognet grpc-spring-boot-starter + org.springframework.boot @@ -130,17 +175,6 @@ protobuf-java-util - - io.pebbletemplates - pebble - 3.1.0 - - - - - redis.clients - jedis - com.google.guava @@ -174,12 +208,14 @@ simpleclient 0.8.0 + io.prometheus simpleclient_hotspot 0.8.0 + io.prometheus @@ -192,17 +228,6 @@ 0.8.0 - - - com.google.cloud - google-cloud-bigquery - - - - com.google.cloud - google-cloud-storage - - com.google.auto.value auto-value-annotations @@ -228,11 +253,9 @@ test - org.mockito mockito-core - 2.23.0 test @@ -248,6 +271,12 @@ com.fasterxml.jackson.dataformat jackson-dataformat-yaml + + + com.github.kstyrc + embedded-redis + test + diff --git a/serving/sample_redis_config.yml b/serving/sample_redis_config.yml deleted file mode 100644 index b3461649a1d..00000000000 --- a/serving/sample_redis_config.yml +++ /dev/null @@ -1,9 +0,0 @@ -name: serving -type: REDIS -redis_config: - host: localhost - port: 6379 -subscriptions: - - name: "*" - project: "*" - version: "*" diff --git a/serving/src/main/java/feast/serving/FeastProperties.java b/serving/src/main/java/feast/serving/FeastProperties.java deleted file mode 100644 index e511835b0aa..00000000000 --- a/serving/src/main/java/feast/serving/FeastProperties.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright 2018-2019 The Feast Authors - * - * 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 feast.serving; - -// Feast configuration properties that maps Feast configuration from default application.yml file to -// a Java object. -// https://www.baeldung.com/configuration-properties-in-spring-boot -// https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-typesafe-configuration-properties - -import java.util.Map; -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = "feast") -public class FeastProperties { - private String version; - private String coreHost; - private int coreGrpcPort; - private StoreProperties store; - private JobProperties jobs; - private TracingProperties tracing; - - public String getVersion() { - return this.version; - } - - public String getCoreHost() { - return this.coreHost; - } - - public int getCoreGrpcPort() { - return this.coreGrpcPort; - } - - public StoreProperties getStore() { - return this.store; - } - - public JobProperties getJobs() { - return this.jobs; - } - - public TracingProperties getTracing() { - return this.tracing; - } - - public void setVersion(String version) { - this.version = version; - } - - public void setCoreHost(String coreHost) { - this.coreHost = coreHost; - } - - public void setCoreGrpcPort(int coreGrpcPort) { - this.coreGrpcPort = coreGrpcPort; - } - - public void setStore(StoreProperties store) { - this.store = store; - } - - public void setJobs(JobProperties jobs) { - this.jobs = jobs; - } - - public void setTracing(TracingProperties tracing) { - this.tracing = tracing; - } - - public static class StoreProperties { - private String configPath; - private int redisPoolMaxSize; - private int redisPoolMaxIdle; - - public String getConfigPath() { - return this.configPath; - } - - public int getRedisPoolMaxSize() { - return this.redisPoolMaxSize; - } - - public int getRedisPoolMaxIdle() { - return this.redisPoolMaxIdle; - } - - public void setConfigPath(String configPath) { - this.configPath = configPath; - } - - public void setRedisPoolMaxSize(int redisPoolMaxSize) { - this.redisPoolMaxSize = redisPoolMaxSize; - } - - public void setRedisPoolMaxIdle(int redisPoolMaxIdle) { - this.redisPoolMaxIdle = redisPoolMaxIdle; - } - } - - public static class JobProperties { - private String stagingLocation; - private String storeType; - private Map storeOptions; - - public String getStagingLocation() { - return this.stagingLocation; - } - - public String getStoreType() { - return this.storeType; - } - - public Map getStoreOptions() { - return this.storeOptions; - } - - public void setStagingLocation(String stagingLocation) { - this.stagingLocation = stagingLocation; - } - - public void setStoreType(String storeType) { - this.storeType = storeType; - } - - public void setStoreOptions(Map storeOptions) { - this.storeOptions = storeOptions; - } - } - - public static class TracingProperties { - private boolean enabled; - private String tracerName; - private String serviceName; - - public boolean isEnabled() { - return this.enabled; - } - - public String getTracerName() { - return this.tracerName; - } - - public String getServiceName() { - return this.serviceName; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public void setTracerName(String tracerName) { - this.tracerName = tracerName; - } - - public void setServiceName(String serviceName) { - this.serviceName = serviceName; - } - } -} diff --git a/serving/src/main/java/feast/serving/ServingApplication.java b/serving/src/main/java/feast/serving/ServingApplication.java index ae9bb87a0b5..ab036d04d18 100644 --- a/serving/src/main/java/feast/serving/ServingApplication.java +++ b/serving/src/main/java/feast/serving/ServingApplication.java @@ -16,11 +16,20 @@ */ package feast.serving; +import feast.serving.config.FeastProperties; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; -@SpringBootApplication +@SpringBootApplication( + exclude = { + DataSourceAutoConfiguration.class, + DataSourceTransactionManagerAutoConfiguration.class, + HibernateJpaAutoConfiguration.class + }) @EnableConfigurationProperties(FeastProperties.class) public class ServingApplication { public static void main(String[] args) { diff --git a/serving/src/main/java/feast/serving/configuration/ContextClosedHandler.java b/serving/src/main/java/feast/serving/config/ContextClosedHandler.java similarity index 96% rename from serving/src/main/java/feast/serving/configuration/ContextClosedHandler.java rename to serving/src/main/java/feast/serving/config/ContextClosedHandler.java index a4f6d64d84f..2bc97439f38 100644 --- a/serving/src/main/java/feast/serving/configuration/ContextClosedHandler.java +++ b/serving/src/main/java/feast/serving/config/ContextClosedHandler.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package feast.serving.configuration; +package feast.serving.config; import java.util.concurrent.ScheduledExecutorService; import org.springframework.beans.factory.annotation.Autowired; diff --git a/serving/src/main/java/feast/serving/config/FeastProperties.java b/serving/src/main/java/feast/serving/config/FeastProperties.java new file mode 100644 index 00000000000..f905f5f5c02 --- /dev/null +++ b/serving/src/main/java/feast/serving/config/FeastProperties.java @@ -0,0 +1,542 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2019 The Feast Authors + * + * 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 feast.serving.config; + +// Feast configuration properties that maps Feast configuration from default application.yml file to +// a Java object. +// https://www.baeldung.com/configuration-properties-in-spring-boot +// https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-typesafe-configuration-properties + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.util.JsonFormat; +import feast.proto.core.StoreProto; +import java.util.*; +import java.util.stream.Collectors; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Positive; +import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidHost; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.info.BuildProperties; + +/** Feast Serving properties. */ +@ConfigurationProperties(prefix = "feast", ignoreInvalidFields = true) +public class FeastProperties { + + /** + * Instantiates a new Feast Serving properties. + * + * @param buildProperties the build properties + */ + @Autowired + public FeastProperties(BuildProperties buildProperties) { + setVersion(buildProperties.getVersion()); + } + + /** Instantiates a new Feast class. */ + public FeastProperties() {} + + /* Feast Serving build version */ + @NotBlank private String version = "unknown"; + + /* Feast Core host to connect to. */ + @ValidHost @NotBlank private String coreHost; + + /* Feast Core port to connect to. */ + @Positive private int coreGrpcPort; + + /** + * Finds and returns the active store + * + * @return Returns the {@link Store} model object + */ + public Store getActiveStore() { + for (Store store : getStores()) { + if (activeStore.equals(store.getName())) { + return store; + } + } + throw new RuntimeException( + String.format("Active store is misconfigured. Could not find store: %s.", activeStore)); + } + + /** + * Set the name of the active store found in the "stores" configuration list + * + * @param activeStore String name to active store + */ + public void setActiveStore(String activeStore) { + this.activeStore = activeStore; + } + + /** Name of the active store configuration (only one store can be active at a time). */ + @NotBlank private String activeStore; + + /** + * Collection of store configurations. The active store is selected by the "activeStore" field. + */ + private List stores = new ArrayList<>(); + + /* Job Store properties to retain state of async jobs. */ + private JobStoreProperties jobStore; + + /* Metric tracing properties. */ + private TracingProperties tracing; + + /** + * Gets Serving store configuration as a list of {@link Store}. + * + * @return List of stores objects + */ + public List getStores() { + return stores; + } + + /** + * Gets Feast Serving build version. + * + * @return the build version + */ + public String getVersion() { + return version; + } + + /** + * Sets build version + * + * @param version the build version + */ + public void setVersion(String version) { + this.version = version; + } + + /** + * Gets Feast Core host. + * + * @return Feast Core host + */ + public String getCoreHost() { + return coreHost; + } + + /** + * Sets Feast Core host to connect to. + * + * @param coreHost Feast Core host + */ + public void setCoreHost(String coreHost) { + this.coreHost = coreHost; + } + + /** + * Gets Feast Core gRPC port. + * + * @return Feast Core gRPC port + */ + public int getCoreGrpcPort() { + return coreGrpcPort; + } + + /** + * Sets Feast Core gRPC port. + * + * @param coreGrpcPort gRPC port of Feast Core + */ + public void setCoreGrpcPort(int coreGrpcPort) { + this.coreGrpcPort = coreGrpcPort; + } + + /** + * Sets the collection of configured stores. + * + * @param stores List of {@link Store} + */ + public void setStores(List stores) { + this.stores = stores; + } + + /** Store configuration class for database that this Feast Serving uses. */ + public static class Store { + + private String name; + + private String type; + + private Map config = new HashMap<>(); + + private List subscriptions = new ArrayList<>(); + + /** + * Gets name of this store. This is unique to this specific instance. + * + * @return the name of the store + */ + public String getName() { + return name; + } + + /** + * Sets the name of this store. + * + * @param name the name of the store + */ + public void setName(String name) { + this.name = name; + } + + /** + * Gets the store type. Example are REDIS or BIGQUERY + * + * @return the store type as a String. + */ + public String getType() { + return type; + } + + /** + * Sets the store type + * + * @param type the type + */ + public void setType(String type) { + this.type = type; + } + + /** + * Converts this {@link Store} to a {@link StoreProto.Store} + * + * @return {@link StoreProto.Store} with configuration set + * @throws InvalidProtocolBufferException the invalid protocol buffer exception + * @throws JsonProcessingException the json processing exception + */ + public StoreProto.Store toProto() + throws InvalidProtocolBufferException, JsonProcessingException { + List subscriptions = getSubscriptions(); + List subscriptionProtos = + subscriptions.stream().map(Subscription::toProto).collect(Collectors.toList()); + + StoreProto.Store.Builder storeProtoBuilder = + StoreProto.Store.newBuilder() + .setName(name) + .setType(StoreProto.Store.StoreType.valueOf(type)) + .addAllSubscriptions(subscriptionProtos); + + ObjectMapper jsonWriter = new ObjectMapper(); + + // TODO: All of this logic should be moved to the store layer. Only a Map + // should be sent to a store and it should do its own validation. + switch (StoreProto.Store.StoreType.valueOf(type)) { + case REDIS_CLUSTER: + StoreProto.Store.RedisClusterConfig.Builder redisClusterConfig = + StoreProto.Store.RedisClusterConfig.newBuilder(); + JsonFormat.parser().merge(jsonWriter.writeValueAsString(config), redisClusterConfig); + return storeProtoBuilder.setRedisClusterConfig(redisClusterConfig.build()).build(); + case REDIS: + StoreProto.Store.RedisConfig.Builder redisConfig = + StoreProto.Store.RedisConfig.newBuilder(); + JsonFormat.parser().merge(jsonWriter.writeValueAsString(config), redisConfig); + return storeProtoBuilder.setRedisConfig(redisConfig.build()).build(); + case BIGQUERY: + StoreProto.Store.BigQueryConfig.Builder bqConfig = + StoreProto.Store.BigQueryConfig.newBuilder(); + JsonFormat.parser().merge(jsonWriter.writeValueAsString(config), bqConfig); + return storeProtoBuilder.setBigqueryConfig(bqConfig.build()).build(); + case CASSANDRA: + StoreProto.Store.CassandraConfig.Builder cassandraConfig = + StoreProto.Store.CassandraConfig.newBuilder(); + JsonFormat.parser().merge(jsonWriter.writeValueAsString(config), cassandraConfig); + return storeProtoBuilder.setCassandraConfig(cassandraConfig.build()).build(); + default: + throw new InvalidProtocolBufferException("Invalid store set"); + } + } + + /** + * Get the subscriptions to this specific store. The subscriptions indicate which feature sets a + * store subscribes to. + * + * @return List of subscriptions. + */ + public List getSubscriptions() { + return subscriptions; + } + + /** + * Sets the store specific configuration. See getSubscriptions() for more details. + * + * @param subscriptions the subscriptions list + */ + public void setSubscriptions(List subscriptions) { + this.subscriptions = subscriptions; + } + + /** + * Gets the configuration to this specific store. This is a map of strings. These options are + * unique to the store. Please see protos/feast/core/Store.proto for the store specific + * configuration options + * + * @return Returns the store specific configuration + */ + public Map getConfig() { + return config; + } + + /** + * Sets the store config. Please protos/feast/core/Store.proto for the specific options for each + * store. + * + * @param config the config map + */ + public void setConfig(Map config) { + this.config = config; + } + + /** + * The Subscription type. + * + *

Note: Please see protos/feast/core/CoreService.proto for details on how to subscribe to + * feature sets. + */ + public static class Subscription { + /** Feast project to subscribe to. */ + String project; + + /** Feature set to subscribe to. */ + String name; + + /** Feature set versions to subscribe to. */ + String version; + + /** + * Gets Feast project subscribed to. + * + * @return the project string + */ + public String getProject() { + return project; + } + + /** + * Sets Feast project to subscribe to for this store. + * + * @param project the project + */ + public void setProject(String project) { + this.project = project; + } + + /** + * Gets the feature set name to subscribe to. + * + * @return the name + */ + public String getName() { + return name; + } + + /** + * Sets the feature set name to subscribe to. + * + * @param name the name + */ + public void setName(String name) { + this.name = name; + } + + /** + * Gets the feature set version that is being subscribed to by this store. + * + * @return the version + */ + public String getVersion() { + return version; + } + + /** + * Sets the feature set version that is being subscribed to by this store. + * + * @param version the version + */ + public void setVersion(String version) { + this.version = version; + } + + /** + * Convert this {@link Subscription} to a {@link StoreProto.Store.Subscription}. + * + * @return the store proto . store . subscription + */ + public StoreProto.Store.Subscription toProto() { + return StoreProto.Store.Subscription.newBuilder() + .setName(getName()) + .setProject(getProject()) + .build(); + } + } + } + + /** + * Gets job store properties + * + * @return the job store properties + */ + public JobStoreProperties getJobStore() { + return jobStore; + } + + /** + * Set job store properties + * + * @param jobStore Job store properties to set + */ + public void setJobStore(JobStoreProperties jobStore) { + this.jobStore = jobStore; + } + + /** + * Gets tracing properties + * + * @return tracing properties + */ + public TracingProperties getTracing() { + return tracing; + } + + /** + * Sets the tracing configuration. + * + * @param tracing the tracing + */ + public void setTracing(TracingProperties tracing) { + this.tracing = tracing; + } + + /** The type Job store properties. */ + public static class JobStoreProperties { + + /** Job Store Redis Host */ + private String redisHost; + + /** Job Store Redis Host */ + private int redisPort; + + /** + * Gets redis host. + * + * @return the redis host + */ + public String getRedisHost() { + return redisHost; + } + + /** + * Sets redis host. + * + * @param redisHost the redis host + */ + public void setRedisHost(String redisHost) { + this.redisHost = redisHost; + } + + /** + * Gets redis port. + * + * @return the redis port + */ + public int getRedisPort() { + return redisPort; + } + + /** + * Sets redis port. + * + * @param redisPort the redis port + */ + public void setRedisPort(int redisPort) { + this.redisPort = redisPort; + } + } + + /** Trace metric collection properties */ + public static class TracingProperties { + + /** Tracing enabled/disabled */ + private boolean enabled; + + /** Name of tracer to use (only "jaeger") */ + private String tracerName; + + /** Service name uniquely identifies this Feast Serving deployment */ + private String serviceName; + + /** + * Is tracing enabled + * + * @return boolean flag + */ + public boolean isEnabled() { + return enabled; + } + + /** + * Sets tracing enabled or disabled. + * + * @param enabled flag + */ + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + /** + * Gets tracer name ('jaeger') + * + * @return the tracer name + */ + public String getTracerName() { + return tracerName; + } + + /** + * Sets tracer name. + * + * @param tracerName the tracer name + */ + public void setTracerName(String tracerName) { + this.tracerName = tracerName; + } + + /** + * Gets the service name. The service name uniquely identifies this Feast serving instance. + * + * @return the service name + */ + public String getServiceName() { + return serviceName; + } + + /** + * Sets service name. + * + * @param serviceName the service name + */ + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + } +} diff --git a/serving/src/main/java/feast/serving/configuration/InstrumentationConfig.java b/serving/src/main/java/feast/serving/config/InstrumentationConfig.java similarity index 96% rename from serving/src/main/java/feast/serving/configuration/InstrumentationConfig.java rename to serving/src/main/java/feast/serving/config/InstrumentationConfig.java index 2cd284829c4..30269c5d0ec 100644 --- a/serving/src/main/java/feast/serving/configuration/InstrumentationConfig.java +++ b/serving/src/main/java/feast/serving/config/InstrumentationConfig.java @@ -14,9 +14,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package feast.serving.configuration; +package feast.serving.config; -import feast.serving.FeastProperties; import io.opentracing.Tracer; import io.opentracing.noop.NoopTracerFactory; import io.prometheus.client.exporter.MetricsServlet; diff --git a/serving/src/main/java/feast/serving/configuration/JobServiceConfig.java b/serving/src/main/java/feast/serving/config/JobServiceConfig.java similarity index 51% rename from serving/src/main/java/feast/serving/configuration/JobServiceConfig.java rename to serving/src/main/java/feast/serving/config/JobServiceConfig.java index 2afbdaf90d1..b85e24062da 100644 --- a/serving/src/main/java/feast/serving/configuration/JobServiceConfig.java +++ b/serving/src/main/java/feast/serving/config/JobServiceConfig.java @@ -14,42 +14,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package feast.serving.configuration; +package feast.serving.config; -import feast.core.StoreProto.Store; -import feast.core.StoreProto.Store.RedisConfig; -import feast.core.StoreProto.Store.StoreType; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.protobuf.InvalidProtocolBufferException; +import feast.proto.core.StoreProto.Store.StoreType; import feast.serving.service.JobService; import feast.serving.service.NoopJobService; import feast.serving.service.RedisBackedJobService; -import feast.serving.specs.CachedSpecService; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import redis.clients.jedis.Jedis; @Configuration public class JobServiceConfig { @Bean - public JobService jobService(Store jobStore, CachedSpecService specService) { - if (!specService.getStore().getType().equals(StoreType.BIGQUERY)) { + public JobService jobService(FeastProperties feastProperties) + throws InvalidProtocolBufferException, JsonProcessingException { + if (!feastProperties.getActiveStore().toProto().getType().equals(StoreType.BIGQUERY)) { return new NoopJobService(); } - - switch (jobStore.getType()) { - case REDIS: - RedisConfig redisConfig = jobStore.getRedisConfig(); - Jedis jedis = new Jedis(redisConfig.getHost(), redisConfig.getPort()); - return new RedisBackedJobService(jedis); - case INVALID: - case BIGQUERY: - case CASSANDRA: - case UNRECOGNIZED: - default: - throw new IllegalArgumentException( - String.format( - "Unsupported store type '%s' for job store name '%s'", - jobStore.getType(), jobStore.getName())); - } + return new RedisBackedJobService(feastProperties.getJobStore()); } } diff --git a/serving/src/main/java/feast/serving/configuration/ServingApiConfiguration.java b/serving/src/main/java/feast/serving/config/ServingApiConfiguration.java similarity index 97% rename from serving/src/main/java/feast/serving/configuration/ServingApiConfiguration.java rename to serving/src/main/java/feast/serving/config/ServingApiConfiguration.java index 539b25a0fcd..ce4fe134373 100644 --- a/serving/src/main/java/feast/serving/configuration/ServingApiConfiguration.java +++ b/serving/src/main/java/feast/serving/config/ServingApiConfiguration.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package feast.serving.configuration; +package feast.serving.config; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; diff --git a/serving/src/main/java/feast/serving/config/ServingServiceConfig.java b/serving/src/main/java/feast/serving/config/ServingServiceConfig.java new file mode 100644 index 00000000000..46ffdb2fcc8 --- /dev/null +++ b/serving/src/main/java/feast/serving/config/ServingServiceConfig.java @@ -0,0 +1,84 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2019 The Feast Authors + * + * 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 feast.serving.config; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.protobuf.InvalidProtocolBufferException; +import feast.proto.core.StoreProto; +import feast.serving.service.HistoricalServingService; +import feast.serving.service.JobService; +import feast.serving.service.NoopJobService; +import feast.serving.service.OnlineServingService; +import feast.serving.service.ServingService; +import feast.serving.specs.CachedSpecService; +import feast.storage.api.retriever.HistoricalRetriever; +import feast.storage.api.retriever.OnlineRetriever; +import feast.storage.connectors.bigquery.retriever.BigQueryHistoricalRetriever; +import feast.storage.connectors.redis.retriever.RedisOnlineRetriever; +import feast.storage.connectors.rediscluster.retriever.RedisClusterOnlineRetriever; +import io.opentracing.Tracer; +import java.util.Map; +import org.slf4j.Logger; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ServingServiceConfig { + + private static final Logger log = org.slf4j.LoggerFactory.getLogger(ServingServiceConfig.class); + + @Bean + public ServingService servingService( + FeastProperties feastProperties, + CachedSpecService specService, + JobService jobService, + Tracer tracer) + throws InvalidProtocolBufferException, JsonProcessingException { + ServingService servingService = null; + FeastProperties.Store store = feastProperties.getActiveStore(); + StoreProto.Store.StoreType storeType = store.toProto().getType(); + Map config = store.getConfig(); + + switch (storeType) { + case REDIS_CLUSTER: + OnlineRetriever redisClusterRetriever = RedisClusterOnlineRetriever.create(config); + servingService = new OnlineServingService(redisClusterRetriever, specService, tracer); + break; + case REDIS: + OnlineRetriever redisRetriever = RedisOnlineRetriever.create(config); + servingService = new OnlineServingService(redisRetriever, specService, tracer); + break; + case BIGQUERY: + if (jobService.getClass() == NoopJobService.class) { + throw new IllegalArgumentException( + "Unable to instantiate JobService which is required by BigQueryHistoricalRetriever."); + } + HistoricalRetriever bqRetriever = BigQueryHistoricalRetriever.create(config); + servingService = new HistoricalServingService(bqRetriever, specService, jobService); + break; + case CASSANDRA: + case UNRECOGNIZED: + case INVALID: + throw new IllegalArgumentException( + String.format( + "Unsupported store type '%s' for store name '%s'", + store.getType(), store.getName())); + } + + return servingService; + } +} diff --git a/serving/src/main/java/feast/serving/configuration/SpecServiceConfig.java b/serving/src/main/java/feast/serving/config/SpecServiceConfig.java similarity index 81% rename from serving/src/main/java/feast/serving/configuration/SpecServiceConfig.java rename to serving/src/main/java/feast/serving/config/SpecServiceConfig.java index 3c91c2765aa..0a62557077f 100644 --- a/serving/src/main/java/feast/serving/configuration/SpecServiceConfig.java +++ b/serving/src/main/java/feast/serving/config/SpecServiceConfig.java @@ -14,13 +14,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package feast.serving.configuration; +package feast.serving.config; -import feast.serving.FeastProperties; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.protobuf.InvalidProtocolBufferException; +import feast.proto.core.StoreProto; import feast.serving.specs.CachedSpecService; import feast.serving.specs.CoreSpecService; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -35,7 +35,7 @@ public class SpecServiceConfig { private static final Logger log = org.slf4j.LoggerFactory.getLogger(SpecServiceConfig.class); private String feastCoreHost; private int feastCorePort; - private static final int CACHE_REFRESH_RATE_MINUTES = 1; + private static final int CACHE_REFRESH_RATE_SECONDS = 10; @Autowired public SpecServiceConfig(FeastProperties feastProperties) { @@ -51,18 +51,18 @@ public ScheduledExecutorService cachedSpecServiceScheduledExecutorService( // reload all specs including new ones periodically scheduledExecutorService.scheduleAtFixedRate( cachedSpecStorage::scheduledPopulateCache, - CACHE_REFRESH_RATE_MINUTES, - CACHE_REFRESH_RATE_MINUTES, - TimeUnit.MINUTES); + CACHE_REFRESH_RATE_SECONDS, + CACHE_REFRESH_RATE_SECONDS, + TimeUnit.SECONDS); return scheduledExecutorService; } @Bean - public CachedSpecService specService(FeastProperties feastProperties) { - + public CachedSpecService specService(FeastProperties feastProperties) + throws InvalidProtocolBufferException, JsonProcessingException { CoreSpecService coreService = new CoreSpecService(feastCoreHost, feastCorePort); - Path path = Paths.get(feastProperties.getStore().getConfigPath()); - CachedSpecService cachedSpecStorage = new CachedSpecService(coreService, path); + StoreProto.Store storeProto = feastProperties.getActiveStore().toProto(); + CachedSpecService cachedSpecStorage = new CachedSpecService(coreService, storeProto); try { cachedSpecStorage.populateCache(); } catch (Exception e) { diff --git a/serving/src/main/java/feast/serving/configuration/ServingServiceConfig.java b/serving/src/main/java/feast/serving/configuration/ServingServiceConfig.java deleted file mode 100644 index 9380ded4c4e..00000000000 --- a/serving/src/main/java/feast/serving/configuration/ServingServiceConfig.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright 2018-2019 The Feast Authors - * - * 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 feast.serving.configuration; - -import com.google.cloud.bigquery.BigQuery; -import com.google.cloud.bigquery.BigQueryOptions; -import com.google.cloud.storage.Storage; -import com.google.cloud.storage.StorageOptions; -import feast.core.StoreProto.Store; -import feast.core.StoreProto.Store.BigQueryConfig; -import feast.core.StoreProto.Store.Builder; -import feast.core.StoreProto.Store.RedisConfig; -import feast.core.StoreProto.Store.StoreType; -import feast.core.StoreProto.Store.Subscription; -import feast.serving.FeastProperties; -import feast.serving.FeastProperties.JobProperties; -import feast.serving.service.BigQueryServingService; -import feast.serving.service.JobService; -import feast.serving.service.NoopJobService; -import feast.serving.service.RedisServingService; -import feast.serving.service.ServingService; -import feast.serving.specs.CachedSpecService; -import io.opentracing.Tracer; -import java.util.Map; -import org.slf4j.Logger; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import redis.clients.jedis.JedisPool; -import redis.clients.jedis.JedisPoolConfig; - -@Configuration -public class ServingServiceConfig { - - private static final Logger log = org.slf4j.LoggerFactory.getLogger(ServingServiceConfig.class); - - @Bean(name = "JobStore") - public Store jobStoreDefinition(FeastProperties feastProperties) { - JobProperties jobProperties = feastProperties.getJobs(); - if (feastProperties.getJobs().getStoreType().equals("")) { - return Store.newBuilder().build(); - } - Map options = jobProperties.getStoreOptions(); - Builder storeDefinitionBuilder = - Store.newBuilder().setType(StoreType.valueOf(jobProperties.getStoreType())); - return setStoreConfig(storeDefinitionBuilder, options); - } - - private Store setStoreConfig(Store.Builder builder, Map options) { - switch (builder.getType()) { - case REDIS: - RedisConfig redisConfig = - RedisConfig.newBuilder() - .setHost(options.get("host")) - .setPort(Integer.parseInt(options.get("port"))) - .build(); - return builder.setRedisConfig(redisConfig).build(); - case BIGQUERY: - BigQueryConfig bqConfig = - BigQueryConfig.newBuilder() - .setProjectId(options.get("projectId")) - .setDatasetId(options.get("datasetId")) - .build(); - return builder.setBigqueryConfig(bqConfig).build(); - case CASSANDRA: - default: - throw new IllegalArgumentException( - String.format( - "Unsupported store %s provided, only REDIS or BIGQUERY are currently supported.", - builder.getType())); - } - } - - @Bean - public ServingService servingService( - FeastProperties feastProperties, - CachedSpecService specService, - JobService jobService, - Tracer tracer) { - ServingService servingService = null; - Store store = specService.getStore(); - - switch (store.getType()) { - case REDIS: - RedisConfig redisConfig = store.getRedisConfig(); - JedisPoolConfig poolConfig = new JedisPoolConfig(); - poolConfig.setMaxTotal(feastProperties.getStore().getRedisPoolMaxSize()); - poolConfig.setMaxIdle(feastProperties.getStore().getRedisPoolMaxIdle()); - JedisPool jedisPool = - new JedisPool(poolConfig, redisConfig.getHost(), redisConfig.getPort()); - servingService = new RedisServingService(jedisPool, specService, tracer); - break; - case BIGQUERY: - BigQueryConfig bqConfig = store.getBigqueryConfig(); - BigQuery bigquery = BigQueryOptions.getDefaultInstance().getService(); - Storage storage = StorageOptions.getDefaultInstance().getService(); - String jobStagingLocation = feastProperties.getJobs().getStagingLocation(); - if (!jobStagingLocation.contains("://")) { - throw new IllegalArgumentException( - String.format("jobStagingLocation is not a valid URI: %s", jobStagingLocation)); - } - if (jobStagingLocation.endsWith("/")) { - jobStagingLocation = jobStagingLocation.substring(0, jobStagingLocation.length() - 1); - } - if (!jobStagingLocation.startsWith("gs://")) { - throw new IllegalArgumentException( - "Store type BIGQUERY requires job staging location to be a valid and existing Google Cloud Storage URI. Invalid staging location: " - + jobStagingLocation); - } - if (jobService.getClass() == NoopJobService.class) { - throw new IllegalArgumentException( - "Unable to instantiate jobService for BigQuery store."); - } - servingService = - new BigQueryServingService( - bigquery, - bqConfig.getProjectId(), - bqConfig.getDatasetId(), - specService, - jobService, - jobStagingLocation, - storage); - break; - case CASSANDRA: - case UNRECOGNIZED: - case INVALID: - throw new IllegalArgumentException( - String.format( - "Unsupported store type '%s' for store name '%s'", - store.getType(), store.getName())); - } - - return servingService; - } - - private Subscription parseSubscription(String subscription) { - String[] split = subscription.split(":"); - return Subscription.newBuilder().setName(split[0]).setVersion(split[1]).build(); - } -} diff --git a/serving/src/main/java/feast/serving/controller/HealthServiceController.java b/serving/src/main/java/feast/serving/controller/HealthServiceController.java index 53728544656..0810429183e 100644 --- a/serving/src/main/java/feast/serving/controller/HealthServiceController.java +++ b/serving/src/main/java/feast/serving/controller/HealthServiceController.java @@ -16,8 +16,9 @@ */ package feast.serving.controller; -import feast.core.StoreProto.Store; -import feast.serving.ServingAPIProto.GetFeastServingInfoRequest; +import feast.proto.core.StoreProto.Store; +import feast.proto.serving.ServingAPIProto.GetFeastServingInfoRequest; +import feast.serving.interceptors.GrpcMonitoringInterceptor; import feast.serving.service.ServingService; import feast.serving.specs.CachedSpecService; import io.grpc.health.v1.HealthGrpc.HealthImplBase; @@ -30,7 +31,7 @@ // Reference: https://github.com/grpc/grpc/blob/master/doc/health-checking.md -@GRpcService +@GRpcService(interceptors = {GrpcMonitoringInterceptor.class}) public class HealthServiceController extends HealthImplBase { private CachedSpecService specService; private ServingService servingService; diff --git a/serving/src/main/java/feast/serving/controller/ServingServiceGRpcController.java b/serving/src/main/java/feast/serving/controller/ServingServiceGRpcController.java index 0eb9d1e3450..3fae6ae65a7 100644 --- a/serving/src/main/java/feast/serving/controller/ServingServiceGRpcController.java +++ b/serving/src/main/java/feast/serving/controller/ServingServiceGRpcController.java @@ -16,18 +16,21 @@ */ package feast.serving.controller; -import feast.serving.FeastProperties; -import feast.serving.ServingAPIProto.GetBatchFeaturesRequest; -import feast.serving.ServingAPIProto.GetBatchFeaturesResponse; -import feast.serving.ServingAPIProto.GetFeastServingInfoRequest; -import feast.serving.ServingAPIProto.GetFeastServingInfoResponse; -import feast.serving.ServingAPIProto.GetJobRequest; -import feast.serving.ServingAPIProto.GetJobResponse; -import feast.serving.ServingAPIProto.GetOnlineFeaturesRequest; -import feast.serving.ServingAPIProto.GetOnlineFeaturesResponse; -import feast.serving.ServingServiceGrpc.ServingServiceImplBase; +import feast.proto.serving.ServingAPIProto.GetBatchFeaturesRequest; +import feast.proto.serving.ServingAPIProto.GetBatchFeaturesResponse; +import feast.proto.serving.ServingAPIProto.GetFeastServingInfoRequest; +import feast.proto.serving.ServingAPIProto.GetFeastServingInfoResponse; +import feast.proto.serving.ServingAPIProto.GetJobRequest; +import feast.proto.serving.ServingAPIProto.GetJobResponse; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequest; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse; +import feast.proto.serving.ServingServiceGrpc.ServingServiceImplBase; +import feast.serving.config.FeastProperties; +import feast.serving.exception.SpecRetrievalException; +import feast.serving.interceptors.GrpcMonitoringInterceptor; import feast.serving.service.ServingService; import feast.serving.util.RequestHelper; +import io.grpc.Status; import io.grpc.stub.StreamObserver; import io.opentracing.Scope; import io.opentracing.Span; @@ -36,7 +39,7 @@ import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; -@GRpcService +@GRpcService(interceptors = {GrpcMonitoringInterceptor.class}) public class ServingServiceGRpcController extends ServingServiceImplBase { private static final Logger log = @@ -73,6 +76,10 @@ public void getOnlineFeatures( GetOnlineFeaturesResponse onlineFeatures = servingService.getOnlineFeatures(request); responseObserver.onNext(onlineFeatures); responseObserver.onCompleted(); + } catch (SpecRetrievalException e) { + log.error("Failed to retrieve specs in SpecService", e); + responseObserver.onError( + Status.NOT_FOUND.withDescription(e.getMessage()).withCause(e).asException()); } catch (Exception e) { log.warn("Failed to get Online Features", e); responseObserver.onError(e); @@ -88,6 +95,10 @@ public void getBatchFeatures( GetBatchFeaturesResponse batchFeatures = servingService.getBatchFeatures(request); responseObserver.onNext(batchFeatures); responseObserver.onCompleted(); + } catch (SpecRetrievalException e) { + log.error("Failed to retrieve specs in SpecService", e); + responseObserver.onError( + Status.NOT_FOUND.withDescription(e.getMessage()).withCause(e).asException()); } catch (Exception e) { log.warn("Failed to get Batch Features", e); responseObserver.onError(e); diff --git a/serving/src/main/java/feast/serving/controller/ServingServiceRestController.java b/serving/src/main/java/feast/serving/controller/ServingServiceRestController.java index b0e349fd6b0..df71112ef78 100644 --- a/serving/src/main/java/feast/serving/controller/ServingServiceRestController.java +++ b/serving/src/main/java/feast/serving/controller/ServingServiceRestController.java @@ -18,11 +18,11 @@ import static feast.serving.util.mappers.ResponseJSONMapper.mapGetOnlineFeaturesResponse; -import feast.serving.FeastProperties; -import feast.serving.ServingAPIProto.GetFeastServingInfoRequest; -import feast.serving.ServingAPIProto.GetFeastServingInfoResponse; -import feast.serving.ServingAPIProto.GetOnlineFeaturesRequest; -import feast.serving.ServingAPIProto.GetOnlineFeaturesResponse; +import feast.proto.serving.ServingAPIProto.GetFeastServingInfoRequest; +import feast.proto.serving.ServingAPIProto.GetFeastServingInfoResponse; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequest; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse; +import feast.serving.config.FeastProperties; import feast.serving.service.ServingService; import feast.serving.util.RequestHelper; import io.opentracing.Tracer; diff --git a/serving/src/main/java/feast/serving/interceptors/GrpcMonitoringInterceptor.java b/serving/src/main/java/feast/serving/interceptors/GrpcMonitoringInterceptor.java new file mode 100644 index 00000000000..bc7ed8997e3 --- /dev/null +++ b/serving/src/main/java/feast/serving/interceptors/GrpcMonitoringInterceptor.java @@ -0,0 +1,55 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.serving.interceptors; + +import feast.serving.util.Metrics; +import io.grpc.ForwardingServerCall.SimpleForwardingServerCall; +import io.grpc.Metadata; +import io.grpc.ServerCall; +import io.grpc.ServerCall.Listener; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; +import io.grpc.Status; + +/** + * GrpcMonitoringInterceptor intercepts GRPC calls to provide request latency histogram metrics in + * the Prometheus client. + */ +public class GrpcMonitoringInterceptor implements ServerInterceptor { + + @Override + public Listener interceptCall( + ServerCall call, Metadata headers, ServerCallHandler next) { + + long startCallMillis = System.currentTimeMillis(); + String fullMethodName = call.getMethodDescriptor().getFullMethodName(); + String methodName = fullMethodName.substring(fullMethodName.indexOf("/") + 1); + + return next.startCall( + new SimpleForwardingServerCall(call) { + @Override + public void close(Status status, Metadata trailers) { + Metrics.requestLatency + .labels(methodName) + .observe((System.currentTimeMillis() - startCallMillis) / 1000f); + Metrics.grpcRequestCount.labels(methodName, status.getCode().name()).inc(); + super.close(status, trailers); + } + }, + headers); + } +} diff --git a/serving/src/main/java/feast/serving/service/BigQueryServingService.java b/serving/src/main/java/feast/serving/service/BigQueryServingService.java deleted file mode 100644 index 53d071b57d1..00000000000 --- a/serving/src/main/java/feast/serving/service/BigQueryServingService.java +++ /dev/null @@ -1,263 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright 2018-2019 The Feast Authors - * - * 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 feast.serving.service; - -import static feast.serving.store.bigquery.QueryTemplater.createEntityTableUUIDQuery; -import static feast.serving.store.bigquery.QueryTemplater.generateFullTableName; -import static feast.serving.util.Metrics.requestLatency; - -import com.google.cloud.bigquery.BigQuery; -import com.google.cloud.bigquery.BigQueryException; -import com.google.cloud.bigquery.Field; -import com.google.cloud.bigquery.FormatOptions; -import com.google.cloud.bigquery.Job; -import com.google.cloud.bigquery.JobInfo; -import com.google.cloud.bigquery.LoadJobConfiguration; -import com.google.cloud.bigquery.QueryJobConfiguration; -import com.google.cloud.bigquery.Schema; -import com.google.cloud.bigquery.Table; -import com.google.cloud.bigquery.TableId; -import com.google.cloud.bigquery.TableInfo; -import com.google.cloud.storage.Storage; -import feast.serving.ServingAPIProto; -import feast.serving.ServingAPIProto.DataFormat; -import feast.serving.ServingAPIProto.DatasetSource; -import feast.serving.ServingAPIProto.FeastServingType; -import feast.serving.ServingAPIProto.GetBatchFeaturesRequest; -import feast.serving.ServingAPIProto.GetBatchFeaturesResponse; -import feast.serving.ServingAPIProto.GetFeastServingInfoRequest; -import feast.serving.ServingAPIProto.GetFeastServingInfoResponse; -import feast.serving.ServingAPIProto.GetJobRequest; -import feast.serving.ServingAPIProto.GetJobResponse; -import feast.serving.ServingAPIProto.GetOnlineFeaturesRequest; -import feast.serving.ServingAPIProto.GetOnlineFeaturesResponse; -import feast.serving.ServingAPIProto.JobStatus; -import feast.serving.ServingAPIProto.JobType; -import feast.serving.specs.CachedSpecService; -import feast.serving.specs.FeatureSetRequest; -import feast.serving.store.bigquery.BatchRetrievalQueryRunnable; -import feast.serving.store.bigquery.QueryTemplater; -import feast.serving.store.bigquery.model.FeatureSetInfo; -import io.grpc.Status; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import java.util.stream.Collectors; -import org.joda.time.Duration; -import org.slf4j.Logger; - -public class BigQueryServingService implements ServingService { - - public static final long TEMP_TABLE_EXPIRY_DURATION_MS = Duration.standardDays(1).getMillis(); - private static final Logger log = org.slf4j.LoggerFactory.getLogger(BigQueryServingService.class); - - private final BigQuery bigquery; - private final String projectId; - private final String datasetId; - private final CachedSpecService specService; - private final JobService jobService; - private final String jobStagingLocation; - private final Storage storage; - - public BigQueryServingService( - BigQuery bigquery, - String projectId, - String datasetId, - CachedSpecService specService, - JobService jobService, - String jobStagingLocation, - Storage storage) { - this.bigquery = bigquery; - this.projectId = projectId; - this.datasetId = datasetId; - this.specService = specService; - this.jobService = jobService; - this.jobStagingLocation = jobStagingLocation; - this.storage = storage; - } - - /** {@inheritDoc} */ - @Override - public GetFeastServingInfoResponse getFeastServingInfo( - GetFeastServingInfoRequest getFeastServingInfoRequest) { - return GetFeastServingInfoResponse.newBuilder() - .setType(FeastServingType.FEAST_SERVING_TYPE_BATCH) - .setJobStagingLocation(jobStagingLocation) - .build(); - } - - /** {@inheritDoc} */ - @Override - public GetOnlineFeaturesResponse getOnlineFeatures(GetOnlineFeaturesRequest getFeaturesRequest) { - throw Status.UNIMPLEMENTED.withDescription("Method not implemented").asRuntimeException(); - } - - /** {@inheritDoc} */ - @Override - public GetBatchFeaturesResponse getBatchFeatures(GetBatchFeaturesRequest getFeaturesRequest) { - long startTime = System.currentTimeMillis(); - List featureSetRequests = - specService.getFeatureSets(getFeaturesRequest.getFeaturesList()); - - Table entityTable; - String entityTableName; - try { - entityTable = loadEntities(getFeaturesRequest.getDatasetSource()); - - TableId entityTableWithUUIDs = generateUUIDs(entityTable); - entityTableName = generateFullTableName(entityTableWithUUIDs); - } catch (Exception e) { - throw Status.INTERNAL - .withDescription("Unable to load entity dataset to Bigquery") - .asRuntimeException(); - } - - Schema entityTableSchema = entityTable.getDefinition().getSchema(); - List entityNames = - entityTableSchema.getFields().stream() - .map(Field::getName) - .filter(name -> !name.equals("event_timestamp")) - .collect(Collectors.toList()); - - List featureSetInfos = QueryTemplater.getFeatureSetInfos(featureSetRequests); - - String feastJobId = UUID.randomUUID().toString(); - ServingAPIProto.Job feastJob = - ServingAPIProto.Job.newBuilder() - .setId(feastJobId) - .setType(JobType.JOB_TYPE_DOWNLOAD) - .setStatus(JobStatus.JOB_STATUS_PENDING) - .build(); - jobService.upsert(feastJob); - - new Thread( - BatchRetrievalQueryRunnable.builder() - .setEntityTableName(entityTableName) - .setBigquery(bigquery) - .setStorage(storage) - .setJobService(jobService) - .setProjectId(projectId) - .setDatasetId(datasetId) - .setFeastJobId(feastJobId) - .setEntityTableColumnNames(entityNames) - .setFeatureSetInfos(featureSetInfos) - .setJobStagingLocation(jobStagingLocation) - .build()) - .start(); - - requestLatency - .labels("getBatchFeatures") - .observe((System.currentTimeMillis() - startTime) / 1000); - return GetBatchFeaturesResponse.newBuilder().setJob(feastJob).build(); - } - - /** {@inheritDoc} */ - @Override - public GetJobResponse getJob(GetJobRequest getJobRequest) { - Optional job = jobService.get(getJobRequest.getJob().getId()); - if (!job.isPresent()) { - throw Status.NOT_FOUND - .withDescription(String.format("Job not found: %s", getJobRequest.getJob().getId())) - .asRuntimeException(); - } - return GetJobResponse.newBuilder().setJob(job.get()).build(); - } - - private Table loadEntities(DatasetSource datasetSource) { - Table loadedEntityTable; - switch (datasetSource.getDatasetSourceCase()) { - case FILE_SOURCE: - try { - // Currently only AVRO format is supported - - if (datasetSource.getFileSource().getDataFormat() != DataFormat.DATA_FORMAT_AVRO) { - throw Status.INVALID_ARGUMENT - .withDescription("Invalid file format, only AVRO is supported.") - .asRuntimeException(); - } - - TableId tableId = TableId.of(projectId, datasetId, createTempTableName()); - log.info("Loading entity rows to: {}.{}.{}", projectId, datasetId, tableId.getTable()); - - LoadJobConfiguration loadJobConfiguration = - LoadJobConfiguration.of( - tableId, datasetSource.getFileSource().getFileUrisList(), FormatOptions.avro()); - loadJobConfiguration = - loadJobConfiguration.toBuilder().setUseAvroLogicalTypes(true).build(); - Job job = bigquery.create(JobInfo.of(loadJobConfiguration)); - job.waitFor(); - - TableInfo expiry = - bigquery - .getTable(tableId) - .toBuilder() - .setExpirationTime(System.currentTimeMillis() + TEMP_TABLE_EXPIRY_DURATION_MS) - .build(); - bigquery.update(expiry); - - loadedEntityTable = bigquery.getTable(tableId); - if (!loadedEntityTable.exists()) { - throw new RuntimeException( - "Unable to create entity dataset table, table already exists"); - } - return loadedEntityTable; - } catch (Exception e) { - log.error("Exception has occurred in loadEntities method: ", e); - throw Status.INTERNAL - .withDescription("Failed to load entity dataset into store: " + e.toString()) - .withCause(e) - .asRuntimeException(); - } - case DATASETSOURCE_NOT_SET: - default: - throw Status.INVALID_ARGUMENT - .withDescription("Data source must be set.") - .asRuntimeException(); - } - } - - private TableId generateUUIDs(Table loadedEntityTable) { - try { - String uuidQuery = - createEntityTableUUIDQuery(generateFullTableName(loadedEntityTable.getTableId())); - QueryJobConfiguration queryJobConfig = - QueryJobConfiguration.newBuilder(uuidQuery) - .setDestinationTable(TableId.of(projectId, datasetId, createTempTableName())) - .build(); - Job queryJob = bigquery.create(JobInfo.of(queryJobConfig)); - queryJob.waitFor(); - TableInfo expiry = - bigquery - .getTable(queryJobConfig.getDestinationTable()) - .toBuilder() - .setExpirationTime(System.currentTimeMillis() + TEMP_TABLE_EXPIRY_DURATION_MS) - .build(); - bigquery.update(expiry); - queryJobConfig = queryJob.getConfiguration(); - return queryJobConfig.getDestinationTable(); - } catch (InterruptedException | BigQueryException e) { - throw Status.INTERNAL - .withDescription("Failed to load entity dataset into store") - .withCause(e) - .asRuntimeException(); - } - } - - public static String createTempTableName() { - return "_" + UUID.randomUUID().toString().replace("-", ""); - } -} diff --git a/serving/src/main/java/feast/serving/service/HistoricalServingService.java b/serving/src/main/java/feast/serving/service/HistoricalServingService.java new file mode 100644 index 00000000000..b4a694e3bfa --- /dev/null +++ b/serving/src/main/java/feast/serving/service/HistoricalServingService.java @@ -0,0 +1,119 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2019 The Feast Authors + * + * 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 feast.serving.service; + +import feast.proto.serving.ServingAPIProto; +import feast.proto.serving.ServingAPIProto.*; +import feast.proto.serving.ServingAPIProto.Job.Builder; +import feast.serving.specs.CachedSpecService; +import feast.storage.api.retriever.FeatureSetRequest; +import feast.storage.api.retriever.HistoricalRetrievalResult; +import feast.storage.api.retriever.HistoricalRetriever; +import io.grpc.Status; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.slf4j.Logger; + +public class HistoricalServingService implements ServingService { + + private static final Logger log = + org.slf4j.LoggerFactory.getLogger(HistoricalServingService.class); + + private final HistoricalRetriever retriever; + private final CachedSpecService specService; + private final JobService jobService; + + public HistoricalServingService( + HistoricalRetriever retriever, CachedSpecService specService, JobService jobService) { + this.retriever = retriever; + this.specService = specService; + this.jobService = jobService; + } + + /** {@inheritDoc} */ + @Override + public GetFeastServingInfoResponse getFeastServingInfo( + GetFeastServingInfoRequest getFeastServingInfoRequest) { + return GetFeastServingInfoResponse.newBuilder() + .setType(FeastServingType.FEAST_SERVING_TYPE_BATCH) + .setJobStagingLocation(retriever.getStagingLocation()) + .build(); + } + + /** {@inheritDoc} */ + @Override + public GetOnlineFeaturesResponse getOnlineFeatures(GetOnlineFeaturesRequest getFeaturesRequest) { + throw Status.UNIMPLEMENTED.withDescription("Method not implemented").asRuntimeException(); + } + + /** {@inheritDoc} */ + @Override + public GetBatchFeaturesResponse getBatchFeatures(GetBatchFeaturesRequest getFeaturesRequest) { + List featureSetRequests = + specService.getFeatureSets(getFeaturesRequest.getFeaturesList()); + String retrievalId = UUID.randomUUID().toString(); + Job runningJob = + Job.newBuilder() + .setId(retrievalId) + .setType(JobType.JOB_TYPE_DOWNLOAD) + .setStatus(JobStatus.JOB_STATUS_RUNNING) + .build(); + jobService.upsert(runningJob); + Thread thread = + new Thread( + new Runnable() { + @Override + public void run() { + HistoricalRetrievalResult result = + retriever.getHistoricalFeatures( + retrievalId, getFeaturesRequest.getDatasetSource(), featureSetRequests); + jobService.upsert(resultToJob(result)); + } + }); + thread.start(); + + return GetBatchFeaturesResponse.newBuilder().setJob(runningJob).build(); + } + + /** {@inheritDoc} */ + @Override + public GetJobResponse getJob(GetJobRequest getJobRequest) { + Optional job = jobService.get(getJobRequest.getJob().getId()); + if (!job.isPresent()) { + throw Status.NOT_FOUND + .withDescription(String.format("Job not found: %s", getJobRequest.getJob().getId())) + .asRuntimeException(); + } + return GetJobResponse.newBuilder().setJob(job.get()).build(); + } + + private Job resultToJob(HistoricalRetrievalResult result) { + Builder builder = + Job.newBuilder() + .setId(result.getId()) + .setType(JobType.JOB_TYPE_DOWNLOAD) + .setStatus(result.getStatus()); + if (result.hasError()) { + return builder.setError(result.getError()).build(); + } + return builder + .addAllFileUris(result.getFileUris()) + .setDataFormat(result.getDataFormat()) + .build(); + } +} diff --git a/serving/src/main/java/feast/serving/service/JobService.java b/serving/src/main/java/feast/serving/service/JobService.java index 96af5cd4d60..3198fffd4cc 100644 --- a/serving/src/main/java/feast/serving/service/JobService.java +++ b/serving/src/main/java/feast/serving/service/JobService.java @@ -16,7 +16,7 @@ */ package feast.serving.service; -import feast.serving.ServingAPIProto.Job; +import feast.proto.serving.ServingAPIProto.Job; import java.util.Optional; // JobService interface specifies the operations to manage Job instances internally in Feast diff --git a/serving/src/main/java/feast/serving/service/NoopJobService.java b/serving/src/main/java/feast/serving/service/NoopJobService.java index 41fd561073d..5407cdcbe51 100644 --- a/serving/src/main/java/feast/serving/service/NoopJobService.java +++ b/serving/src/main/java/feast/serving/service/NoopJobService.java @@ -16,7 +16,7 @@ */ package feast.serving.service; -import feast.serving.ServingAPIProto.Job; +import feast.proto.serving.ServingAPIProto.Job; import java.util.Optional; // No-op implementation of the JobService, for online serving stores. diff --git a/serving/src/main/java/feast/serving/service/OnlineServingService.java b/serving/src/main/java/feast/serving/service/OnlineServingService.java new file mode 100644 index 00000000000..d5c615059d1 --- /dev/null +++ b/serving/src/main/java/feast/serving/service/OnlineServingService.java @@ -0,0 +1,170 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2019 The Feast Authors + * + * 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 feast.serving.service; + +import com.google.common.collect.Maps; +import com.google.protobuf.Duration; +import feast.proto.serving.ServingAPIProto.*; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequest.EntityRow; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse.FieldValues; +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.proto.types.ValueProto.Value; +import feast.serving.specs.CachedSpecService; +import feast.serving.util.Metrics; +import feast.serving.util.RefUtil; +import feast.storage.api.retriever.FeatureSetRequest; +import feast.storage.api.retriever.OnlineRetriever; +import io.grpc.Status; +import io.opentracing.Scope; +import io.opentracing.Tracer; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.slf4j.Logger; + +public class OnlineServingService implements ServingService { + + private static final Logger log = org.slf4j.LoggerFactory.getLogger(OnlineServingService.class); + private final CachedSpecService specService; + private final Tracer tracer; + private final OnlineRetriever retriever; + + public OnlineServingService( + OnlineRetriever retriever, CachedSpecService specService, Tracer tracer) { + this.retriever = retriever; + this.specService = specService; + this.tracer = tracer; + } + + /** {@inheritDoc} */ + @Override + public GetFeastServingInfoResponse getFeastServingInfo( + GetFeastServingInfoRequest getFeastServingInfoRequest) { + return GetFeastServingInfoResponse.newBuilder() + .setType(FeastServingType.FEAST_SERVING_TYPE_ONLINE) + .build(); + } + + /** {@inheritDoc} */ + @Override + public GetOnlineFeaturesResponse getOnlineFeatures(GetOnlineFeaturesRequest request) { + try (Scope scope = tracer.buildSpan("getOnlineFeatures").startActive(true)) { + GetOnlineFeaturesResponse.Builder getOnlineFeaturesResponseBuilder = + GetOnlineFeaturesResponse.newBuilder(); + List featureSetRequests = + specService.getFeatureSets(request.getFeaturesList()); + List entityRows = request.getEntityRowsList(); + Map> featureValuesMap = + entityRows.stream() + .collect(Collectors.toMap(row -> row, row -> Maps.newHashMap(row.getFieldsMap()))); + // Get all feature rows from the retriever. Each feature row list corresponds to a single + // feature set request. + List> featureRows = + retriever.getOnlineFeatures(entityRows, featureSetRequests); + + // For each feature set request, read the feature rows returned by the retriever, and + // populate the featureValuesMap with the feature values corresponding to that entity row. + for (var fsIdx = 0; fsIdx < featureRows.size(); fsIdx++) { + List featureRowsForFs = featureRows.get(fsIdx); + FeatureSetRequest featureSetRequest = featureSetRequests.get(fsIdx); + + String project = featureSetRequest.getSpec().getProject(); + + // In order to return values containing the same feature references provided by the user, + // we reuse the feature references in the request as the keys in the featureValuesMap + Map refsByName = featureSetRequest.getFeatureRefsByName(); + + // Each feature row returned (per feature set request) corresponds to a given entity row. + // For each feature row, update the featureValuesMap. + for (var entityRowIdx = 0; entityRowIdx < entityRows.size(); entityRowIdx++) { + FeatureRow featureRow = featureRowsForFs.get(entityRowIdx); + EntityRow entityRow = entityRows.get(entityRowIdx); + + // If the row is stale, put an empty value into the featureValuesMap. + if (isStale(featureSetRequest, entityRow, featureRow)) { + featureSetRequest + .getFeatureReferences() + .parallelStream() + .forEach( + ref -> { + populateStaleKeyCountMetrics(project, ref); + featureValuesMap + .get(entityRow) + .put(RefUtil.generateFeatureStringRef(ref), Value.newBuilder().build()); + }); + + } else { + populateRequestCountMetrics(featureSetRequest); + + // Else populate the featureValueMap at this entityRow with the values in the feature + // row. + featureRow.getFieldsList().stream() + .filter(field -> refsByName.containsKey(field.getName())) + .forEach( + field -> { + FeatureReference ref = refsByName.get(field.getName()); + String id = RefUtil.generateFeatureStringRef(ref); + featureValuesMap.get(entityRow).put(id, field.getValue()); + }); + } + } + } + + List fieldValues = + featureValuesMap.values().stream() + .map(valueMap -> FieldValues.newBuilder().putAllFields(valueMap).build()) + .collect(Collectors.toList()); + return getOnlineFeaturesResponseBuilder.addAllFieldValues(fieldValues).build(); + } + } + + private void populateStaleKeyCountMetrics(String project, FeatureReference ref) { + Metrics.staleKeyCount.labels(project, ref.getName()).inc(); + } + + private void populateRequestCountMetrics(FeatureSetRequest featureSetRequest) { + String project = featureSetRequest.getSpec().getProject(); + featureSetRequest + .getFeatureReferences() + .parallelStream() + .forEach(ref -> Metrics.requestCount.labels(project, ref.getName()).inc()); + } + + @Override + public GetBatchFeaturesResponse getBatchFeatures(GetBatchFeaturesRequest getFeaturesRequest) { + throw Status.UNIMPLEMENTED.withDescription("Method not implemented").asRuntimeException(); + } + + @Override + public GetJobResponse getJob(GetJobRequest getJobRequest) { + throw Status.UNIMPLEMENTED.withDescription("Method not implemented").asRuntimeException(); + } + + private boolean isStale( + FeatureSetRequest featureSetRequest, EntityRow entityRow, FeatureRow featureRow) { + Duration maxAge = featureSetRequest.getSpec().getMaxAge(); + if (maxAge.equals(Duration.getDefaultInstance())) { + return false; + } + long givenTimestamp = entityRow.getEntityTimestamp().getSeconds(); + if (givenTimestamp == 0) { + givenTimestamp = System.currentTimeMillis() / 1000; + } + long timeDifference = givenTimestamp - featureRow.getEventTimestamp().getSeconds(); + return timeDifference > maxAge.getSeconds(); + } +} diff --git a/serving/src/main/java/feast/serving/service/RedisBackedJobService.java b/serving/src/main/java/feast/serving/service/RedisBackedJobService.java index 7bfce552254..9081b03f518 100644 --- a/serving/src/main/java/feast/serving/service/RedisBackedJobService.java +++ b/serving/src/main/java/feast/serving/service/RedisBackedJobService.java @@ -17,12 +17,18 @@ package feast.serving.service; import com.google.protobuf.util.JsonFormat; -import feast.serving.ServingAPIProto.Job; -import feast.serving.ServingAPIProto.Job.Builder; +import feast.proto.serving.ServingAPIProto.Job; +import feast.proto.serving.ServingAPIProto.Job.Builder; +import feast.serving.config.FeastProperties; +import io.lettuce.core.RedisClient; +import io.lettuce.core.RedisURI; +import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.core.api.sync.RedisCommands; +import io.lettuce.core.codec.ByteArrayCodec; +import io.lettuce.core.resource.DefaultClientResources; import java.util.Optional; import org.joda.time.Duration; import org.slf4j.Logger; -import redis.clients.jedis.Jedis; // TODO: Do rate limiting, currently if clients call get() or upsert() // and an exceedingly high rate e.g. they wrap job reload in a while loop with almost no wait @@ -31,38 +37,49 @@ public class RedisBackedJobService implements JobService { private static final Logger log = org.slf4j.LoggerFactory.getLogger(RedisBackedJobService.class); - private final Jedis jedis; + private final RedisCommands syncCommand; // Remove job state info after "defaultExpirySeconds" to prevent filling up Redis memory // and since users normally don't require info about relatively old jobs. private final int defaultExpirySeconds = (int) Duration.standardDays(1).getStandardSeconds(); - public RedisBackedJobService(Jedis jedis) { - this.jedis = jedis; + public RedisBackedJobService(FeastProperties.JobStoreProperties jobStoreProperties) { + RedisURI uri = + RedisURI.create(jobStoreProperties.getRedisHost(), jobStoreProperties.getRedisPort()); + + this.syncCommand = + RedisClient.create(DefaultClientResources.create(), uri) + .connect(new ByteArrayCodec()) + .sync(); + } + + public RedisBackedJobService(StatefulRedisConnection connection) { + this.syncCommand = connection.sync(); } @Override public Optional get(String id) { - String json = jedis.get(id); - if (json == null) { - return Optional.empty(); - } Job job = null; - Builder builder = Job.newBuilder(); try { + String json = new String(syncCommand.get(id.getBytes())); + if (json.isEmpty()) { + return Optional.empty(); + } + Builder builder = Job.newBuilder(); JsonFormat.parser().merge(json, builder); job = builder.build(); } catch (Exception e) { log.error(String.format("Failed to parse JSON for Feast job: %s", e.getMessage())); } - return Optional.ofNullable(job); } @Override public void upsert(Job job) { try { - jedis.set(job.getId(), JsonFormat.printer().omittingInsignificantWhitespace().print(job)); - jedis.expire(job.getId(), defaultExpirySeconds); + syncCommand.set( + job.getId().getBytes(), + JsonFormat.printer().omittingInsignificantWhitespace().print(job).getBytes()); + syncCommand.expire(job.getId().getBytes(), defaultExpirySeconds); } catch (Exception e) { log.error(String.format("Failed to upsert job: %s", e.getMessage())); } diff --git a/serving/src/main/java/feast/serving/service/RedisServingService.java b/serving/src/main/java/feast/serving/service/RedisServingService.java deleted file mode 100644 index 48fc485214d..00000000000 --- a/serving/src/main/java/feast/serving/service/RedisServingService.java +++ /dev/null @@ -1,320 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright 2018-2019 The Feast Authors - * - * 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 feast.serving.service; - -import static feast.serving.util.Metrics.missingKeyCount; -import static feast.serving.util.Metrics.requestCount; -import static feast.serving.util.Metrics.requestLatency; -import static feast.serving.util.Metrics.staleKeyCount; -import static feast.serving.util.RefUtil.generateFeatureSetStringRef; -import static feast.serving.util.RefUtil.generateFeatureStringRef; - -import com.google.common.collect.Maps; -import com.google.protobuf.AbstractMessageLite; -import com.google.protobuf.Duration; -import com.google.protobuf.InvalidProtocolBufferException; -import feast.core.FeatureSetProto.EntitySpec; -import feast.core.FeatureSetProto.FeatureSetSpec; -import feast.serving.ServingAPIProto.FeastServingType; -import feast.serving.ServingAPIProto.FeatureReference; -import feast.serving.ServingAPIProto.GetBatchFeaturesRequest; -import feast.serving.ServingAPIProto.GetBatchFeaturesResponse; -import feast.serving.ServingAPIProto.GetFeastServingInfoRequest; -import feast.serving.ServingAPIProto.GetFeastServingInfoResponse; -import feast.serving.ServingAPIProto.GetJobRequest; -import feast.serving.ServingAPIProto.GetJobResponse; -import feast.serving.ServingAPIProto.GetOnlineFeaturesRequest; -import feast.serving.ServingAPIProto.GetOnlineFeaturesRequest.EntityRow; -import feast.serving.ServingAPIProto.GetOnlineFeaturesResponse; -import feast.serving.ServingAPIProto.GetOnlineFeaturesResponse.FieldValues; -import feast.serving.specs.CachedSpecService; -import feast.serving.specs.FeatureSetRequest; -import feast.serving.util.RefUtil; -import feast.storage.RedisProto.RedisKey; -import feast.types.FeatureRowProto.FeatureRow; -import feast.types.FieldProto.Field; -import feast.types.ValueProto.Value; -import io.grpc.Status; -import io.opentracing.Scope; -import io.opentracing.Tracer; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import org.slf4j.Logger; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisPool; - -public class RedisServingService implements ServingService { - - private static final Logger log = org.slf4j.LoggerFactory.getLogger(RedisServingService.class); - private final JedisPool jedisPool; - private final CachedSpecService specService; - private final Tracer tracer; - - public RedisServingService(JedisPool jedisPool, CachedSpecService specService, Tracer tracer) { - this.jedisPool = jedisPool; - this.specService = specService; - this.tracer = tracer; - } - - /** {@inheritDoc} */ - @Override - public GetFeastServingInfoResponse getFeastServingInfo( - GetFeastServingInfoRequest getFeastServingInfoRequest) { - return GetFeastServingInfoResponse.newBuilder() - .setType(FeastServingType.FEAST_SERVING_TYPE_ONLINE) - .build(); - } - - /** {@inheritDoc} */ - @Override - public GetOnlineFeaturesResponse getOnlineFeatures(GetOnlineFeaturesRequest request) { - try (Scope scope = tracer.buildSpan("Redis-getOnlineFeatures").startActive(true)) { - long startTime = System.currentTimeMillis(); - GetOnlineFeaturesResponse.Builder getOnlineFeaturesResponseBuilder = - GetOnlineFeaturesResponse.newBuilder(); - - List entityRows = request.getEntityRowsList(); - Map> featureValuesMap = - entityRows.stream() - .collect(Collectors.toMap(row -> row, row -> Maps.newHashMap(row.getFieldsMap()))); - List featureSetRequests = - specService.getFeatureSets(request.getFeaturesList()); - for (FeatureSetRequest featureSetRequest : featureSetRequests) { - - List featureSetEntityNames = - featureSetRequest.getSpec().getEntitiesList().stream() - .map(EntitySpec::getName) - .collect(Collectors.toList()); - - List redisKeys = - getRedisKeys(featureSetEntityNames, entityRows, featureSetRequest.getSpec()); - - try { - sendAndProcessMultiGet(redisKeys, entityRows, featureValuesMap, featureSetRequest); - } catch (InvalidProtocolBufferException e) { - throw Status.INTERNAL - .withDescription("Unable to parse protobuf while retrieving feature") - .withCause(e) - .asRuntimeException(); - } - } - List fieldValues = - featureValuesMap.values().stream() - .map(valueMap -> FieldValues.newBuilder().putAllFields(valueMap).build()) - .collect(Collectors.toList()); - requestLatency - .labels("getOnlineFeatures") - .observe((System.currentTimeMillis() - startTime) / 1000); - return getOnlineFeaturesResponseBuilder.addAllFieldValues(fieldValues).build(); - } - } - - @Override - public GetBatchFeaturesResponse getBatchFeatures(GetBatchFeaturesRequest getFeaturesRequest) { - throw Status.UNIMPLEMENTED.withDescription("Method not implemented").asRuntimeException(); - } - - @Override - public GetJobResponse getJob(GetJobRequest getJobRequest) { - throw Status.UNIMPLEMENTED.withDescription("Method not implemented").asRuntimeException(); - } - - /** - * Build the redis keys for retrieval from the store. - * - * @param featureSetEntityNames entity names that actually belong to the featureSet - * @param entityRows entity values to retrieve for - * @param featureSetSpec featureSetSpec of the features to retrieve - * @return list of RedisKeys - */ - private List getRedisKeys( - List featureSetEntityNames, - List entityRows, - FeatureSetSpec featureSetSpec) { - try (Scope scope = tracer.buildSpan("Redis-makeRedisKeys").startActive(true)) { - String featureSetRef = generateFeatureSetStringRef(featureSetSpec); - List redisKeys = - entityRows.stream() - .map(row -> makeRedisKey(featureSetRef, featureSetEntityNames, row)) - .collect(Collectors.toList()); - return redisKeys; - } - } - - /** - * Create {@link RedisKey} - * - * @param featureSet featureSet reference of the feature. E.g. feature_set_1:1 - * @param featureSetEntityNames entity names that belong to the featureSet - * @param entityRow entityRow to build the key from - * @return {@link RedisKey} - */ - private RedisKey makeRedisKey( - String featureSet, List featureSetEntityNames, EntityRow entityRow) { - RedisKey.Builder builder = RedisKey.newBuilder().setFeatureSet(featureSet); - Map fieldsMap = entityRow.getFieldsMap(); - featureSetEntityNames.sort(String::compareTo); - for (int i = 0; i < featureSetEntityNames.size(); i++) { - String entityName = featureSetEntityNames.get(i); - - if (!fieldsMap.containsKey(entityName)) { - throw Status.INVALID_ARGUMENT - .withDescription( - String.format( - "Entity row fields \"%s\" does not contain required entity field \"%s\"", - fieldsMap.keySet().toString(), entityName)) - .asRuntimeException(); - } - - builder.addEntities( - Field.newBuilder().setName(entityName).setValue(fieldsMap.get(entityName))); - } - return builder.build(); - } - - private void sendAndProcessMultiGet( - List redisKeys, - List entityRows, - Map> featureValuesMap, - FeatureSetRequest featureSetRequest) - throws InvalidProtocolBufferException { - - List jedisResps = sendMultiGet(redisKeys); - long startTime = System.currentTimeMillis(); - try (Scope scope = tracer.buildSpan("Redis-processResponse").startActive(true)) { - FeatureSetSpec spec = featureSetRequest.getSpec(); - - Map nullValues = - featureSetRequest.getFeatureReferences().stream() - .collect( - Collectors.toMap( - RefUtil::generateFeatureStringRef, - featureReference -> Value.newBuilder().build())); - - for (int i = 0; i < jedisResps.size(); i++) { - EntityRow entityRow = entityRows.get(i); - Map featureValues = featureValuesMap.get(entityRow); - - byte[] jedisResponse = jedisResps.get(i); - if (jedisResponse == null) { - featureSetRequest - .getFeatureReferences() - .parallelStream() - .forEach( - request -> - missingKeyCount - .labels( - spec.getProject(), - String.format("%s:%d", request.getName(), request.getVersion())) - .inc()); - featureValues.putAll(nullValues); - continue; - } - - FeatureRow featureRow = FeatureRow.parseFrom(jedisResponse); - - boolean stale = isStale(featureSetRequest, entityRow, featureRow); - if (stale) { - featureSetRequest - .getFeatureReferences() - .parallelStream() - .forEach( - request -> - staleKeyCount - .labels( - spec.getProject(), - String.format("%s:%d", request.getName(), request.getVersion())) - .inc()); - featureValues.putAll(nullValues); - continue; - } - - featureSetRequest - .getFeatureReferences() - .parallelStream() - .forEach( - request -> - requestCount - .labels( - spec.getProject(), - String.format("%s:%d", request.getName(), request.getVersion())) - .inc()); - - Map featureNames = - featureSetRequest.getFeatureReferences().stream() - .collect( - Collectors.toMap( - FeatureReference::getName, featureReference -> featureReference)); - featureRow.getFieldsList().stream() - .filter(field -> featureNames.keySet().contains(field.getName())) - .forEach( - field -> { - FeatureReference ref = featureNames.get(field.getName()); - String id = generateFeatureStringRef(ref); - featureValues.put(id, field.getValue()); - }); - } - } finally { - requestLatency - .labels("processResponse") - .observe((System.currentTimeMillis() - startTime) / 1000); - } - } - - private boolean isStale( - FeatureSetRequest featureSetRequest, EntityRow entityRow, FeatureRow featureRow) { - if (featureSetRequest.getSpec().getMaxAge().equals(Duration.getDefaultInstance())) { - return false; - } - long givenTimestamp = entityRow.getEntityTimestamp().getSeconds(); - if (givenTimestamp == 0) { - givenTimestamp = System.currentTimeMillis() / 1000; - } - long timeDifference = givenTimestamp - featureRow.getEventTimestamp().getSeconds(); - return timeDifference > featureSetRequest.getSpec().getMaxAge().getSeconds(); - } - - /** - * Send a list of get request as an mget - * - * @param keys list of {@link RedisKey} - * @return list of {@link FeatureRow} in primitive byte representation for each {@link RedisKey} - */ - private List sendMultiGet(List keys) { - try (Scope scope = tracer.buildSpan("Redis-sendMultiGet").startActive(true)) { - long startTime = System.currentTimeMillis(); - try (Jedis jedis = jedisPool.getResource()) { - byte[][] binaryKeys = - keys.stream() - .map(AbstractMessageLite::toByteArray) - .collect(Collectors.toList()) - .toArray(new byte[0][0]); - return jedis.mget(binaryKeys); - } catch (Exception e) { - throw Status.NOT_FOUND - .withDescription("Unable to retrieve feature from Redis") - .withCause(e) - .asRuntimeException(); - } finally { - requestLatency - .labels("sendMultiGet") - .observe((System.currentTimeMillis() - startTime) / 1000); - } - } - } -} diff --git a/serving/src/main/java/feast/serving/service/ServingService.java b/serving/src/main/java/feast/serving/service/ServingService.java index 83adcb73ba8..1fe9840d594 100644 --- a/serving/src/main/java/feast/serving/service/ServingService.java +++ b/serving/src/main/java/feast/serving/service/ServingService.java @@ -16,22 +16,85 @@ */ package feast.serving.service; -import feast.serving.ServingAPIProto.GetBatchFeaturesRequest; -import feast.serving.ServingAPIProto.GetBatchFeaturesResponse; -import feast.serving.ServingAPIProto.GetFeastServingInfoRequest; -import feast.serving.ServingAPIProto.GetFeastServingInfoResponse; -import feast.serving.ServingAPIProto.GetJobRequest; -import feast.serving.ServingAPIProto.GetJobResponse; -import feast.serving.ServingAPIProto.GetOnlineFeaturesRequest; -import feast.serving.ServingAPIProto.GetOnlineFeaturesResponse; +import feast.proto.serving.ServingAPIProto.GetBatchFeaturesRequest; +import feast.proto.serving.ServingAPIProto.GetBatchFeaturesResponse; +import feast.proto.serving.ServingAPIProto.GetFeastServingInfoRequest; +import feast.proto.serving.ServingAPIProto.GetFeastServingInfoResponse; +import feast.proto.serving.ServingAPIProto.GetJobRequest; +import feast.proto.serving.ServingAPIProto.GetJobResponse; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequest; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse; public interface ServingService { + /** + * Get information about the Feast serving deployment. + * + *

For Bigquery deployments, this includes the default job staging location to load + * intermediate files to. Otherwise, this method only returns the current Feast Serving backing + * store type. + * + * @param getFeastServingInfoRequest {@link GetFeastServingInfoRequest} + * @return {@link GetFeastServingInfoResponse} + */ GetFeastServingInfoResponse getFeastServingInfo( GetFeastServingInfoRequest getFeastServingInfoRequest); + /** + * Get features from an online serving store, given a list of {@link + * feast.proto.serving.ServingAPIProto.FeatureReference}s to retrieve, and list of {@link + * feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequest.EntityRow}s to join the retrieved + * values to. + * + *

Features can be queried across feature sets, but each {@link + * feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequest.EntityRow} must contain all + * entities for all feature sets included in the request. + * + *

This request is fulfilled synchronously. + * + * @param getFeaturesRequest {@link GetOnlineFeaturesRequest} containing list of {@link + * feast.proto.serving.ServingAPIProto.FeatureReference}s to retrieve and list of {@link + * feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequest.EntityRow}s to join the + * retrieved values to. + * @return {@link GetOnlineFeaturesResponse} with list of {@link + * feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse.FieldValues} for each {@link + * feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequest.EntityRow} supplied. + */ GetOnlineFeaturesResponse getOnlineFeatures(GetOnlineFeaturesRequest getFeaturesRequest); + /** + * Get features from a batch serving store, given a list of {@link + * feast.proto.serving.ServingAPIProto.FeatureReference}s to retrieve, and {@link + * feast.proto.serving.ServingAPIProto.DatasetSource} pointing to remote location of dataset to + * join retrieved features to. All columns in the provided dataset will be preserved in the output + * dataset. + * + *

Due to the potential size of batch retrieval requests, this request is fulfilled + * asynchronously, and returns a retrieval job id, which when supplied to {@link + * #getJob(GetJobRequest)} will return the status of the retrieval job. + * + * @param getFeaturesRequest {@link GetBatchFeaturesRequest} containing a list of {@link + * feast.proto.serving.ServingAPIProto.FeatureReference}s to retrieve, and {@link + * feast.proto.serving.ServingAPIProto.DatasetSource} pointing to remote location of dataset + * to join retrieved features to. + * @return {@link GetBatchFeaturesResponse} containing reference to a retrieval {@link + * feast.proto.serving.ServingAPIProto.Job}. + */ GetBatchFeaturesResponse getBatchFeatures(GetBatchFeaturesRequest getFeaturesRequest); + /** + * Get the status of a retrieval job from a batch serving store. + * + *

The client should check the status of the returned job periodically by calling ReloadJob to + * determine if the job has completed successfully or with an error. If the job completes + * successfully i.e. status = JOB_STATUS_DONE with no error, then the client can check the + * file_uris for the location to download feature values data. The client is assumed to have + * access to these file URIs. + * + *

If an error occurred during retrieval, the {@link GetJobResponse} will also contain the + * error that resulted in termination. + * + * @param getJobRequest {@link GetJobRequest} containing reference to a retrieval job + * @return {@link GetJobResponse} + */ GetJobResponse getJob(GetJobRequest getJobRequest); } diff --git a/serving/src/main/java/feast/serving/specs/CachedSpecService.java b/serving/src/main/java/feast/serving/specs/CachedSpecService.java index 040a870ffe1..07b0b8bbbd2 100644 --- a/serving/src/main/java/feast/serving/specs/CachedSpecService.java +++ b/serving/src/main/java/feast/serving/specs/CachedSpecService.java @@ -18,52 +18,49 @@ import static feast.serving.util.RefUtil.generateFeatureSetStringRef; import static feast.serving.util.RefUtil.generateFeatureStringRef; -import static feast.serving.util.mappers.YamlToProtoMapper.yamlToStoreProto; -import static java.util.Comparator.comparingInt; import static java.util.stream.Collectors.groupingBy; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; -import feast.core.CoreServiceProto.ListFeatureSetsRequest; -import feast.core.CoreServiceProto.ListFeatureSetsResponse; -import feast.core.CoreServiceProto.UpdateStoreRequest; -import feast.core.CoreServiceProto.UpdateStoreResponse; -import feast.core.FeatureSetProto.FeatureSet; -import feast.core.FeatureSetProto.FeatureSetSpec; -import feast.core.FeatureSetProto.FeatureSpec; -import feast.core.StoreProto.Store; -import feast.core.StoreProto.Store.Subscription; -import feast.serving.ServingAPIProto.FeatureReference; +import feast.proto.core.CoreServiceProto.ListFeatureSetsRequest; +import feast.proto.core.CoreServiceProto.ListFeatureSetsResponse; +import feast.proto.core.FeatureSetProto.FeatureSet; +import feast.proto.core.FeatureSetProto.FeatureSetSpec; +import feast.proto.core.FeatureSetProto.FeatureSpec; +import feast.proto.core.StoreProto; +import feast.proto.core.StoreProto.Store; +import feast.proto.core.StoreProto.Store.Subscription; +import feast.proto.serving.ServingAPIProto.FeatureReference; import feast.serving.exception.SpecRetrievalException; +import feast.storage.api.retriever.FeatureSetRequest; import io.grpc.StatusRuntimeException; import io.prometheus.client.Gauge; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import org.apache.commons.lang3.tuple.Pair; -import org.apache.commons.lang3.tuple.Triple; import org.slf4j.Logger; -/** In-memory cache of specs. */ +/** In-memory cache of specs hosted in Feast Core. */ public class CachedSpecService { private static final int MAX_SPEC_COUNT = 1000; private static final Logger log = org.slf4j.LoggerFactory.getLogger(CachedSpecService.class); + private static final String DEFAULT_PROJECT_NAME = "default"; + // flag to signal that multiple featuresets match a specific + // string feature reference in the feature to featureset mapping. + private static final String FEATURE_SET_CONFLICT_FLAG = "##CONFLICT##"; private final CoreSpecService coreService; - private final Path configPath; - private final Map featureToFeatureSetMapping; + private Map featureToFeatureSetMapping; - private final CacheLoader featureSetCacheLoader; private final LoadingCache featureSetCache; private Store store; @@ -80,17 +77,17 @@ public class CachedSpecService { .help("epoch time of the last time the cache was updated") .register(); - public CachedSpecService(CoreSpecService coreService, Path configPath) { - this.configPath = configPath; + public CachedSpecService(CoreSpecService coreService, StoreProto.Store store) { this.coreService = coreService; - this.store = updateStore(readConfig(configPath)); + this.store = coreService.registerStore(store); Map featureSets = getFeatureSetMap(); featureToFeatureSetMapping = new ConcurrentHashMap<>(getFeatureToFeatureSetMapping(featureSets)); - featureSetCacheLoader = CacheLoader.from(featureSets::get); + CacheLoader featureSetCacheLoader = CacheLoader.from(featureSets::get); featureSetCache = CacheBuilder.newBuilder().maximumSize(MAX_SPEC_COUNT).build(featureSetCacheLoader); + featureSetCache.putAll(featureSets); } /** @@ -102,8 +99,14 @@ public Store getStore() { return this.store; } + public FeatureSetSpec getFeatureSetSpec(String featureSetRef) throws ExecutionException { + return featureSetCache.get(featureSetRef); + } + /** - * Get FeatureSetSpecs for the given features. + * Get FeatureSetSpecs for the given features references. If the project is unspecified in the + * given references, autofills the default project. Throws a {@link SpecRetrievalException}. If + * multiple feature sets match given string reference, * * @return FeatureSetRequest containing the specs, and their respective feature references */ @@ -112,14 +115,22 @@ public List getFeatureSets(List featureRefe featureReferences.stream() .map( featureReference -> { - String featureSet = - featureToFeatureSetMapping.getOrDefault( - generateFeatureStringRef(featureReference), ""); - if (featureSet.isEmpty()) { + // map feature reference to coresponding feature set name + String fsName = + featureToFeatureSetMapping.get(generateFeatureStringRef(featureReference)); + if (fsName == null) { + throw new SpecRetrievalException( + String.format( + "Unable to find Feature Set for the given Feature Reference: %s", + generateFeatureStringRef(featureReference))); + } else if (fsName == FEATURE_SET_CONFLICT_FLAG) { throw new SpecRetrievalException( - String.format("Unable to retrieve feature %s", featureReference)); + String.format( + "Given Feature Reference is amibigous as it matches multiple Feature Sets: %s." + + "Please specify a more specific Feature Reference (ie specify the project or feature set)", + generateFeatureStringRef(featureReference))); } - return Pair.of(featureSet, featureReference); + return Pair.of(fsName, featureReference); }) .collect(groupingBy(Pair::getLeft)) .forEach( @@ -128,6 +139,19 @@ public List getFeatureSets(List featureRefe FeatureSetSpec featureSetSpec = featureSetCache.get(fsName); List requestedFeatures = featureRefs.stream().map(Pair::getRight).collect(Collectors.toList()); + + // check that requested features reference point to different features in the + // featureset. + HashSet featureNames = new HashSet<>(); + requestedFeatures.forEach( + ref -> { + if (featureNames.contains(ref.getName())) { + throw new SpecRetrievalException( + "Multiple Feature References referencing the same feature in a featureset is not allowed."); + } + featureNames.add(ref.getName()); + }); + FeatureSetRequest featureSetRequest = FeatureSetRequest.newBuilder() .setSpec(featureSetSpec) @@ -136,7 +160,7 @@ public List getFeatureSets(List featureRefe featureSetRequests.add(featureSetRequest); } catch (ExecutionException e) { throw new SpecRetrievalException( - String.format("Unable to retrieve featureSet with id %s", fsName), e); + String.format("Unable to find featureSet with name: %s", fsName), e); } }); return featureSetRequests; @@ -147,10 +171,12 @@ public List getFeatureSets(List featureRefe * from core to preload the cache. */ public void populateCache() { - this.store = updateStore(readConfig(configPath)); Map featureSetMap = getFeatureSetMap(); + + featureSetCache.invalidateAll(); featureSetCache.putAll(featureSetMap); - featureToFeatureSetMapping.putAll(getFeatureToFeatureSetMapping(featureSetMap)); + + featureToFeatureSetMapping = getFeatureToFeatureSetMapping(featureSetMap); featureSetsCount.set(featureSetCache.size()); cacheLastUpdated.set(System.currentTimeMillis()); @@ -175,8 +201,7 @@ private Map getFeatureSetMap() { .setFilter( ListFeatureSetsRequest.Filter.newBuilder() .setProject(subscription.getProject()) - .setFeatureSetName(subscription.getName()) - .setFeatureSetVersion(subscription.getVersion())) + .setFeatureSetName(subscription.getName())) .build()); for (FeatureSet featureSet : featureSetsResponse.getFeatureSetsList()) { @@ -191,72 +216,81 @@ private Map getFeatureSetMap() { return featureSets; } + /** + * Generate a feature to feature set mapping from the given feature sets map. Accounts for + * variations (missing project, feature_set) in string feature references generated by creating + * multiple entries in the returned mapping for each variation. + * + * @param featureSets map of feature set name to feature set specs + * @return mapping of string feature references to name of feature sets + */ private Map getFeatureToFeatureSetMapping( Map featureSets) { - HashMap mapping = new HashMap<>(); + Map mapping = new HashMap<>(); featureSets.values().stream() - .collect( - groupingBy( - featureSet -> - Triple.of( - featureSet.getProject(), featureSet.getName(), featureSet.getVersion()))) .forEach( - (group, groupedFeatureSets) -> { - groupedFeatureSets = - groupedFeatureSets.stream() - .sorted(comparingInt(FeatureSetSpec::getVersion)) - .collect(Collectors.toList()); - for (int i = 0; i < groupedFeatureSets.size(); i++) { - FeatureSetSpec featureSetSpec = groupedFeatureSets.get(i); - for (FeatureSpec featureSpec : featureSetSpec.getFeaturesList()) { - FeatureReference featureRef = - FeatureReference.newBuilder() - .setProject(featureSetSpec.getProject()) - .setName(featureSpec.getName()) - .setVersion(featureSetSpec.getVersion()) - .build(); - mapping.put( - generateFeatureStringRef(featureRef), - generateFeatureSetStringRef(featureSetSpec)); - if (i == groupedFeatureSets.size() - 1) { - featureRef = - FeatureReference.newBuilder() - .setProject(featureSetSpec.getProject()) - .setName(featureSpec.getName()) - .build(); - mapping.put( - generateFeatureStringRef(featureRef), - generateFeatureSetStringRef(featureSetSpec)); + featureSetSpec -> { + for (FeatureSpec featureSpec : featureSetSpec.getFeaturesList()) { + // Register the different permutations of string feature references + // that refers to this feature in the feature to featureset mapping. + + // Features in FeatureSets in default project can be referenced without project. + boolean isInDefaultProject = + featureSetSpec.getProject().equals(DEFAULT_PROJECT_NAME); + + for (boolean hasProject : new boolean[] {true, false}) { + if (!isInDefaultProject && !hasProject) continue; + // Features can be referenced without a featureset if there are no conflicts. + for (boolean hasFeatureSet : new boolean[] {true, false}) { + // Get mapping between string feature reference and featureset + Pair singleMapping = + this.generateFeatureToFeatureSetMapping( + featureSpec, featureSetSpec, hasProject, hasFeatureSet); + String featureRef = singleMapping.getKey(); + String featureSetRef = singleMapping.getValue(); + // Check if another feature set has already mapped to this + // string feature reference. if so mark the conflict. + if (mapping.containsKey(featureRef)) { + mapping.put(featureRef, FEATURE_SET_CONFLICT_FLAG); + } else { + mapping.put(featureRef, featureSetRef); + } } } } }); + return mapping; } - private Store readConfig(Path path) { - try { - List fileContents = Files.readAllLines(path); - String yaml = fileContents.stream().reduce("", (l1, l2) -> l1 + "\n" + l2); - log.info("loaded store config at {}: \n{}", path.toString(), yaml); - return yamlToStoreProto(yaml); - } catch (IOException e) { - throw new RuntimeException( - String.format("Unable to read store config at %s", path.toAbsolutePath()), e); + /** + * Generate a single mapping between the given feature and the featureset. Maps a feature + * reference refering to the given feature to the corresponding featureset's name. + * + * @param featureSpec specifying the feature to create mapping for. + * @param featureSetSpec specifying the feature set to create mapping for. + * @param hasProject whether generated mapping's string feature ref has a project. + * @param hasFeatureSet whether generated mapping's string feature ref has a featureSet. + * @return a pair mapping a string feature reference to a featureset name. + */ + private Pair generateFeatureToFeatureSetMapping( + FeatureSpec featureSpec, + FeatureSetSpec featureSetSpec, + boolean hasProject, + boolean hasFeatureSet) { + FeatureReference.Builder featureRef = + FeatureReference.newBuilder() + .setProject(featureSetSpec.getProject()) + .setFeatureSet(featureSetSpec.getName()) + .setName(featureSpec.getName()); + if (!hasProject) { + featureRef = featureRef.clearProject(); } - } - - private Store updateStore(Store store) { - UpdateStoreRequest request = UpdateStoreRequest.newBuilder().setStore(store).build(); - try { - UpdateStoreResponse updateStoreResponse = coreService.updateStore(request); - if (!updateStoreResponse.getStore().equals(store)) { - throw new RuntimeException("Core store config not matching current store config"); - } - return updateStoreResponse.getStore(); - } catch (Exception e) { - throw new RuntimeException("Unable to update store configuration", e); + if (!hasFeatureSet) { + featureRef = featureRef.clearFeatureSet(); } + return Pair.of( + generateFeatureStringRef(featureRef.build()), generateFeatureSetStringRef(featureSetSpec)); } } diff --git a/serving/src/main/java/feast/serving/specs/CoreSpecService.java b/serving/src/main/java/feast/serving/specs/CoreSpecService.java index 2f5cef342e0..e2feaebccb2 100644 --- a/serving/src/main/java/feast/serving/specs/CoreSpecService.java +++ b/serving/src/main/java/feast/serving/specs/CoreSpecService.java @@ -16,18 +16,19 @@ */ package feast.serving.specs; -import feast.core.CoreServiceGrpc; -import feast.core.CoreServiceProto.GetFeatureSetRequest; -import feast.core.CoreServiceProto.GetFeatureSetResponse; -import feast.core.CoreServiceProto.ListFeatureSetsRequest; -import feast.core.CoreServiceProto.ListFeatureSetsResponse; -import feast.core.CoreServiceProto.UpdateStoreRequest; -import feast.core.CoreServiceProto.UpdateStoreResponse; +import feast.proto.core.CoreServiceGrpc; +import feast.proto.core.CoreServiceProto.GetFeatureSetRequest; +import feast.proto.core.CoreServiceProto.GetFeatureSetResponse; +import feast.proto.core.CoreServiceProto.ListFeatureSetsRequest; +import feast.proto.core.CoreServiceProto.ListFeatureSetsResponse; +import feast.proto.core.CoreServiceProto.UpdateStoreRequest; +import feast.proto.core.CoreServiceProto.UpdateStoreResponse; +import feast.proto.core.StoreProto.Store; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import org.slf4j.Logger; -/** Client for spec retrieval from core. */ +/** Client for interfacing with specs in Feast Core. */ public class CoreSpecService { private static final Logger log = org.slf4j.LoggerFactory.getLogger(CoreSpecService.class); @@ -50,4 +51,24 @@ public ListFeatureSetsResponse listFeatureSets(ListFeatureSetsRequest ListFeatur public UpdateStoreResponse updateStore(UpdateStoreRequest updateStoreRequest) { return blockingStub.updateStore(updateStoreRequest); } + + /** + * Register the given store entry in Feast Core. If store already exists in Feast Core, updates + * the store entry in feast core. + * + * @param store entry to register/update in Feast Core. + * @return The register/updated store entry + */ + public Store registerStore(Store store) { + UpdateStoreRequest request = UpdateStoreRequest.newBuilder().setStore(store).build(); + try { + UpdateStoreResponse updateStoreResponse = this.updateStore(request); + if (!updateStoreResponse.getStore().equals(store)) { + throw new RuntimeException("Core store config not matching current store config"); + } + return updateStoreResponse.getStore(); + } catch (Exception e) { + throw new RuntimeException("Unable to update store configuration", e); + } + } } diff --git a/serving/src/main/java/feast/serving/store/bigquery/BatchRetrievalQueryRunnable.java b/serving/src/main/java/feast/serving/store/bigquery/BatchRetrievalQueryRunnable.java deleted file mode 100644 index d437294dfc3..00000000000 --- a/serving/src/main/java/feast/serving/store/bigquery/BatchRetrievalQueryRunnable.java +++ /dev/null @@ -1,296 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright 2018-2019 The Feast Authors - * - * 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 feast.serving.store.bigquery; - -import static feast.serving.service.BigQueryServingService.TEMP_TABLE_EXPIRY_DURATION_MS; -import static feast.serving.service.BigQueryServingService.createTempTableName; -import static feast.serving.store.bigquery.QueryTemplater.createTimestampLimitQuery; - -import com.google.auto.value.AutoValue; -import com.google.cloud.bigquery.BigQuery; -import com.google.cloud.bigquery.BigQueryException; -import com.google.cloud.bigquery.DatasetId; -import com.google.cloud.bigquery.ExtractJobConfiguration; -import com.google.cloud.bigquery.FieldValueList; -import com.google.cloud.bigquery.Job; -import com.google.cloud.bigquery.JobInfo; -import com.google.cloud.bigquery.QueryJobConfiguration; -import com.google.cloud.bigquery.TableId; -import com.google.cloud.bigquery.TableInfo; -import com.google.cloud.bigquery.TableResult; -import com.google.cloud.storage.Blob; -import com.google.cloud.storage.Storage; -import com.google.cloud.storage.Storage.BlobListOption; -import feast.serving.ServingAPIProto; -import feast.serving.ServingAPIProto.DataFormat; -import feast.serving.ServingAPIProto.JobStatus; -import feast.serving.ServingAPIProto.JobType; -import feast.serving.service.JobService; -import feast.serving.store.bigquery.model.FeatureSetInfo; -import io.grpc.Status; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorCompletionService; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -@AutoValue -public abstract class BatchRetrievalQueryRunnable implements Runnable { - - private static final long SUBQUERY_TIMEOUT_SECS = 900; // 15 minutes - - public abstract JobService jobService(); - - public abstract String projectId(); - - public abstract String datasetId(); - - public abstract String feastJobId(); - - public abstract BigQuery bigquery(); - - public abstract List entityTableColumnNames(); - - public abstract List featureSetInfos(); - - public abstract String entityTableName(); - - public abstract String jobStagingLocation(); - - public abstract Storage storage(); - - public static Builder builder() { - return new AutoValue_BatchRetrievalQueryRunnable.Builder(); - } - - @AutoValue.Builder - public abstract static class Builder { - public abstract Builder setJobService(JobService jobService); - - public abstract Builder setProjectId(String projectId); - - public abstract Builder setDatasetId(String datasetId); - - public abstract Builder setFeastJobId(String feastJobId); - - public abstract Builder setBigquery(BigQuery bigquery); - - public abstract Builder setEntityTableColumnNames(List entityTableColumnNames); - - public abstract Builder setFeatureSetInfos(List featureSetInfos); - - public abstract Builder setEntityTableName(String entityTableName); - - public abstract Builder setJobStagingLocation(String jobStagingLocation); - - public abstract Builder setStorage(Storage storage); - - public abstract BatchRetrievalQueryRunnable build(); - } - - @Override - public void run() { - - FieldValueList timestampLimits = getTimestampLimits(entityTableName()); - - List featureSetQueries = generateQueries(timestampLimits); - - QueryJobConfiguration queryConfig; - - try { - Job queryJob = runBatchQuery(featureSetQueries); - queryConfig = queryJob.getConfiguration(); - String exportTableDestinationUri = - String.format("%s/%s/*.avro", jobStagingLocation(), feastJobId()); - - // Hardcode the format to Avro for now - ExtractJobConfiguration extractConfig = - ExtractJobConfiguration.of( - queryConfig.getDestinationTable(), exportTableDestinationUri, "Avro"); - Job extractJob = bigquery().create(JobInfo.of(extractConfig)); - extractJob.waitFor(); - } catch (BigQueryException | InterruptedException | IOException e) { - jobService() - .upsert( - ServingAPIProto.Job.newBuilder() - .setId(feastJobId()) - .setType(JobType.JOB_TYPE_DOWNLOAD) - .setStatus(JobStatus.JOB_STATUS_DONE) - .setError(e.getMessage()) - .build()); - return; - } - - List fileUris = parseOutputFileURIs(); - - jobService() - .upsert( - ServingAPIProto.Job.newBuilder() - .setId(feastJobId()) - .setType(JobType.JOB_TYPE_DOWNLOAD) - .setStatus(JobStatus.JOB_STATUS_DONE) - .addAllFileUris(fileUris) - .setDataFormat(DataFormat.DATA_FORMAT_AVRO) - .build()); - } - - private List parseOutputFileURIs() { - String scheme = jobStagingLocation().substring(0, jobStagingLocation().indexOf("://")); - String stagingLocationNoScheme = - jobStagingLocation().substring(jobStagingLocation().indexOf("://") + 3); - String bucket = stagingLocationNoScheme.split("/")[0]; - List prefixParts = new ArrayList<>(); - prefixParts.add( - stagingLocationNoScheme.contains("/") && !stagingLocationNoScheme.endsWith("/") - ? stagingLocationNoScheme.substring(stagingLocationNoScheme.indexOf("/") + 1) - : ""); - prefixParts.add(feastJobId()); - String prefix = String.join("/", prefixParts) + "/"; - - List fileUris = new ArrayList<>(); - for (Blob blob : storage().list(bucket, BlobListOption.prefix(prefix)).iterateAll()) { - fileUris.add(String.format("%s://%s/%s", scheme, blob.getBucket(), blob.getName())); - } - return fileUris; - } - - Job runBatchQuery(List featureSetQueries) - throws BigQueryException, InterruptedException, IOException { - Job queryJob; - ExecutorService executorService = Executors.newFixedThreadPool(featureSetQueries.size()); - ExecutorCompletionService executorCompletionService = - new ExecutorCompletionService<>(executorService); - - List featureSetInfos = new ArrayList<>(); - - for (int i = 0; i < featureSetQueries.size(); i++) { - QueryJobConfiguration queryJobConfig = - QueryJobConfiguration.newBuilder(featureSetQueries.get(i)) - .setDestinationTable(TableId.of(projectId(), datasetId(), createTempTableName())) - .build(); - Job subqueryJob = bigquery().create(JobInfo.of(queryJobConfig)); - executorCompletionService.submit( - SubqueryCallable.builder() - .setBigquery(bigquery()) - .setFeatureSetInfo(featureSetInfos().get(i)) - .setSubqueryJob(subqueryJob) - .build()); - } - - for (int i = 0; i < featureSetQueries.size(); i++) { - try { - FeatureSetInfo featureSetInfo = - executorCompletionService.take().get(SUBQUERY_TIMEOUT_SECS, TimeUnit.SECONDS); - featureSetInfos.add(featureSetInfo); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - jobService() - .upsert( - ServingAPIProto.Job.newBuilder() - .setId(feastJobId()) - .setType(JobType.JOB_TYPE_DOWNLOAD) - .setStatus(JobStatus.JOB_STATUS_DONE) - .setError(e.getMessage()) - .build()); - - executorService.shutdownNow(); - throw Status.INTERNAL - .withDescription("Error running batch query") - .withCause(e) - .asRuntimeException(); - } - } - - String joinQuery = - QueryTemplater.createJoinQuery( - featureSetInfos, entityTableColumnNames(), entityTableName()); - QueryJobConfiguration queryJobConfig = - QueryJobConfiguration.newBuilder(joinQuery) - .setDestinationTable(TableId.of(projectId(), datasetId(), createTempTableName())) - .build(); - queryJob = bigquery().create(JobInfo.of(queryJobConfig)); - queryJob.waitFor(); - - TableInfo expiry = - bigquery() - .getTable(queryJobConfig.getDestinationTable()) - .toBuilder() - .setExpirationTime(System.currentTimeMillis() + TEMP_TABLE_EXPIRY_DURATION_MS) - .build(); - bigquery().update(expiry); - - return queryJob; - } - - private List generateQueries(FieldValueList timestampLimits) { - List featureSetQueries = new ArrayList<>(); - try { - for (FeatureSetInfo featureSetInfo : featureSetInfos()) { - String query = - QueryTemplater.createFeatureSetPointInTimeQuery( - featureSetInfo, - projectId(), - datasetId(), - entityTableName(), - timestampLimits.get("min").getStringValue(), - timestampLimits.get("max").getStringValue()); - featureSetQueries.add(query); - } - } catch (IOException e) { - throw Status.INTERNAL - .withDescription("Unable to generate query for batch retrieval") - .withCause(e) - .asRuntimeException(); - } - return featureSetQueries; - } - - private FieldValueList getTimestampLimits(String entityTableName) { - QueryJobConfiguration getTimestampLimitsQuery = - QueryJobConfiguration.newBuilder(createTimestampLimitQuery(entityTableName)) - .setDefaultDataset(DatasetId.of(projectId(), datasetId())) - .setDestinationTable(TableId.of(projectId(), datasetId(), createTempTableName())) - .build(); - try { - Job job = bigquery().create(JobInfo.of(getTimestampLimitsQuery)); - TableResult getTimestampLimitsQueryResult = job.waitFor().getQueryResults(); - TableInfo expiry = - bigquery() - .getTable(getTimestampLimitsQuery.getDestinationTable()) - .toBuilder() - .setExpirationTime(System.currentTimeMillis() + TEMP_TABLE_EXPIRY_DURATION_MS) - .build(); - bigquery().update(expiry); - FieldValueList result = null; - for (FieldValueList fields : getTimestampLimitsQueryResult.getValues()) { - result = fields; - } - if (result == null || result.get("min").isNull() || result.get("max").isNull()) { - throw new RuntimeException("query returned insufficient values"); - } - return result; - } catch (InterruptedException e) { - throw Status.INTERNAL - .withDescription("Unable to extract min and max timestamps from query") - .withCause(e) - .asRuntimeException(); - } - } -} diff --git a/serving/src/main/java/feast/serving/util/Metrics.java b/serving/src/main/java/feast/serving/util/Metrics.java index 99f6353e742..fa66f79a804 100644 --- a/serving/src/main/java/feast/serving/util/Metrics.java +++ b/serving/src/main/java/feast/serving/util/Metrics.java @@ -24,9 +24,9 @@ public class Metrics { public static final Histogram requestLatency = Histogram.build() .buckets(0.001, 0.002, 0.004, 0.006, 0.008, 0.01, 0.015, 0.02, 0.025, 0.03, 0.035, 0.05) - .name("request_latency_ms") + .name("request_latency_seconds") .subsystem("feast_serving") - .help("Request latency in seconds.") + .help("Request latency in seconds") .labelNames("method") .register(); @@ -46,6 +46,14 @@ public class Metrics { .labelNames("project", "feature_name") .register(); + public static final Counter invalidEncodingCount = + Counter.build() + .name("invalid_encoding_feature_count") + .subsystem("feast_serving") + .help("number requested feature rows that were stored with the wrong encoding") + .labelNames("project", "feature_name") + .register(); + public static final Counter staleKeyCount = Counter.build() .name("stale_feature_count") @@ -53,4 +61,12 @@ public class Metrics { .help("number requested feature rows that were stale") .labelNames("project", "feature_name") .register(); + + public static final Counter grpcRequestCount = + Counter.build() + .name("grpc_request_count") + .subsystem("feast_serving") + .help("number of grpc requests served") + .labelNames("method", "status_code") + .register(); } diff --git a/serving/src/main/java/feast/serving/util/RefUtil.java b/serving/src/main/java/feast/serving/util/RefUtil.java index 74de3e65620..e3c36f1f9f6 100644 --- a/serving/src/main/java/feast/serving/util/RefUtil.java +++ b/serving/src/main/java/feast/serving/util/RefUtil.java @@ -16,23 +16,23 @@ */ package feast.serving.util; -import feast.core.FeatureSetProto.FeatureSetSpec; -import feast.serving.ServingAPIProto.FeatureReference; +import feast.proto.core.FeatureSetProto.FeatureSetSpec; +import feast.proto.serving.ServingAPIProto.FeatureReference; public class RefUtil { public static String generateFeatureStringRef(FeatureReference featureReference) { - String ref = String.format("%s/%s", featureReference.getProject(), featureReference.getName()); - if (featureReference.getVersion() > 0) { - return ref + String.format(":%d", featureReference.getVersion()); + String ref = featureReference.getName(); + if (!featureReference.getFeatureSet().isEmpty()) { + ref = featureReference.getFeatureSet() + ":" + ref; + } + if (!featureReference.getProject().isEmpty()) { + ref = featureReference.getProject() + "/" + ref; } return ref; } public static String generateFeatureSetStringRef(FeatureSetSpec featureSetSpec) { String ref = String.format("%s/%s", featureSetSpec.getProject(), featureSetSpec.getName()); - if (featureSetSpec.getVersion() > 0) { - return ref + String.format(":%d", featureSetSpec.getVersion()); - } return ref; } } diff --git a/serving/src/main/java/feast/serving/util/RequestHelper.java b/serving/src/main/java/feast/serving/util/RequestHelper.java index e6e8e8629a1..16073e9b5c0 100644 --- a/serving/src/main/java/feast/serving/util/RequestHelper.java +++ b/serving/src/main/java/feast/serving/util/RequestHelper.java @@ -16,9 +16,9 @@ */ package feast.serving.util; -import feast.serving.ServingAPIProto.FeatureReference; -import feast.serving.ServingAPIProto.GetBatchFeaturesRequest; -import feast.serving.ServingAPIProto.GetOnlineFeaturesRequest; +import feast.proto.serving.ServingAPIProto.FeatureReference; +import feast.proto.serving.ServingAPIProto.GetBatchFeaturesRequest; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequest; import io.grpc.Status; import java.util.Set; import java.util.stream.Collectors; diff --git a/serving/src/main/java/feast/serving/util/mappers/ResponseJSONMapper.java b/serving/src/main/java/feast/serving/util/mappers/ResponseJSONMapper.java index 14723efe7b1..6aba17ac431 100644 --- a/serving/src/main/java/feast/serving/util/mappers/ResponseJSONMapper.java +++ b/serving/src/main/java/feast/serving/util/mappers/ResponseJSONMapper.java @@ -16,9 +16,9 @@ */ package feast.serving.util.mappers; -import feast.serving.ServingAPIProto.GetOnlineFeaturesResponse; -import feast.serving.ServingAPIProto.GetOnlineFeaturesResponse.FieldValues; -import feast.types.ValueProto.Value; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse.FieldValues; +import feast.proto.types.ValueProto.Value; import java.util.List; import java.util.Map; import java.util.stream.Collectors; diff --git a/serving/src/main/java/feast/serving/util/mappers/YamlToProtoMapper.java b/serving/src/main/java/feast/serving/util/mappers/YamlToProtoMapper.java deleted file mode 100644 index 00ad1fabb1c..00000000000 --- a/serving/src/main/java/feast/serving/util/mappers/YamlToProtoMapper.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright 2018-2019 The Feast Authors - * - * 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 feast.serving.util.mappers; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import com.google.protobuf.util.JsonFormat; -import feast.core.StoreProto.Store; -import feast.core.StoreProto.Store.Builder; -import java.io.IOException; - -public class YamlToProtoMapper { - private static final ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory()); - private static final ObjectMapper jsonWriter = new ObjectMapper(); - - public static Store yamlToStoreProto(String yaml) throws IOException { - Object obj = yamlReader.readValue(yaml, Object.class); - String jsonString = jsonWriter.writeValueAsString(obj); - Builder builder = Store.newBuilder(); - JsonFormat.parser().merge(jsonString, builder); - return builder.build(); - } -} diff --git a/serving/src/main/proto/feast b/serving/src/main/proto/feast deleted file mode 120000 index d520da9126b..00000000000 --- a/serving/src/main/proto/feast +++ /dev/null @@ -1 +0,0 @@ -../../../../protos/feast \ No newline at end of file diff --git a/serving/src/main/proto/third_party b/serving/src/main/proto/third_party deleted file mode 120000 index 363d20598e6..00000000000 --- a/serving/src/main/proto/third_party +++ /dev/null @@ -1 +0,0 @@ -../../../../protos/third_party \ No newline at end of file diff --git a/serving/src/main/resources/application.yml b/serving/src/main/resources/application.yml index 2daa83fbfb2..9158ee7fa58 100644 --- a/serving/src/main/resources/application.yml +++ b/serving/src/main/resources/application.yml @@ -1,12 +1,49 @@ feast: - # This value is retrieved from project.version properties in pom.xml - # https://docs.spring.io/spring-boot/docs/current/reference/html/ - version: @project.version@ # GRPC service address for Feast Core # Feast Serving requires connection to Feast Core to retrieve and reload Feast metadata (e.g. FeatureSpecs, Store information) core-host: ${FEAST_CORE_HOST:localhost} core-grpc-port: ${FEAST_CORE_GRPC_PORT:6565} + # Indicates the active store. Only a single store in the last can be active at one time. In the future this key + # will be deprecated in order to allow multiple stores to be served from a single serving instance + active_store: online + + # List of store configurations + stores: + # Below are two store configurations. One for Redis and one for BigQuery. + # Please see https://api.docs.feast.dev/grpc/feast.core.pb.html#Store for configuration options + - name: online # Name of the store (referenced by active_store) + type: REDIS # Type of the store. REDIS, BIGQUERY are available options + config: # Store specific configuration. See + host: localhost + port: 6379 + # Subscriptions indicate which feature sets needs to be retrieved and used to populate this store + subscriptions: + # Wildcards match all options. No filtering is done. + - name: "*" + project: "*" + + - name: historical + type: BIGQUERY + config: # Store specific configuration. + # GCP Project + project_id: my_project + # BigQuery Dataset Id + dataset_id: my_dataset + # staging-location specifies the URI to store intermediate files for batch serving. + # Feast Serving client is expected to have read access to this staging location + # to download the batch features. + # For example: gs://mybucket/myprefix + # Please omit the trailing slash in the URI. + staging_location: gs://mybucket/myprefix + # Retry options for BigQuery retrieval jobs + initial_retry_delay_seconds: 1 + # BigQuery timeout for retrieval jobs + total_timeout_seconds: 21600 + subscriptions: + - name: "*" + project: "*" + tracing: # If true, Feast will provide tracing data (using OpenTracing API) for various RPC method calls # which can be useful to debug performance issues and perform benchmarking @@ -17,32 +54,13 @@ feast: # The service name identifier for the tracing data service-name: feast_serving - store: - # Path containing the store configuration for this serving store. - config-path: ${FEAST_STORE_CONFIG_PATH:serving/sample_redis_config.yml} - # If serving redis, the redis pool max size - redis-pool-max-size: ${FEAST_REDIS_POOL_MAX_SIZE:128} - # If serving redis, the redis pool max idle conns - redis-pool-max-idle: ${FEAST_REDIS_POOL_MAX_IDLE:16} - - jobs: - # job-staging-location specifies the URI to store intermediate files for batch serving. - # Feast Serving client is expected to have read access to this staging location - # to download the batch features. - # - # For example: gs://mybucket/myprefix - # Please omit the trailing slash in the URI. - staging-location: ${FEAST_JOB_STAGING_LOCATION:} - # Type of store to store job metadata. This only needs to be set if the - # serving store type is Bigquery. - store-type: ${FEAST_JOB_STORE_TYPE:} - # - # Job store connection options. If the job store is redis, the following items are required: - # - # store-options: - # host: localhost - # port: 6379 - store-options: {} + # The job store is used to maintain job management state for Feast Serving. This is required when using certain + # historical stores like BigQuery. Only Redis is supported as a job store. + job_store: + # Redis host to connect to + redis_host: localhost + # Redis port to connect to + redis_port: 6379 grpc: # The port number Feast Serving GRPC service should listen on diff --git a/serving/src/main/resources/log4j2.xml b/serving/src/main/resources/log4j2.xml index 02520cb36c2..661c8e5061c 100644 --- a/serving/src/main/resources/log4j2.xml +++ b/serving/src/main/resources/log4j2.xml @@ -21,19 +21,23 @@ %d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${hostName} --- [%15.15t] %-40.40c{1.} : %m%n%ex + ${env:LOG_TYPE:-Console} + ${env:LOG_LEVEL:-info} + + + - - + + - - - + + \ No newline at end of file diff --git a/serving/src/main/resources/templates/join_featuresets.sql b/serving/src/main/resources/templates/join_featuresets.sql deleted file mode 100644 index e57b0c10314..00000000000 --- a/serving/src/main/resources/templates/join_featuresets.sql +++ /dev/null @@ -1,21 +0,0 @@ -WITH joined as ( -SELECT * FROM `{{ leftTableName }}` -{% for featureSet in featureSets %} -LEFT JOIN ( - SELECT - uuid, - {% for featureName in featureSet.features %} - {{ featureSet.project }}_{{ featureName }}_v{{ featureSet.version }}{% if loop.last %}{% else %}, {% endif %} - {% endfor %} - FROM `{{ featureSet.table }}` -) USING (uuid) -{% endfor %} -) SELECT - event_timestamp, - {{ entities | join(', ') }} - {% for featureSet in featureSets %} - {% for featureName in featureSet.features %} - ,{{ featureSet.project }}_{{ featureName }}_v{{ featureSet.version }} as {{ featureName }} - {% endfor %} - {% endfor %} -FROM joined \ No newline at end of file diff --git a/serving/src/main/resources/templates/single_featureset_pit_join.sql b/serving/src/main/resources/templates/single_featureset_pit_join.sql deleted file mode 100644 index f6678421851..00000000000 --- a/serving/src/main/resources/templates/single_featureset_pit_join.sql +++ /dev/null @@ -1,55 +0,0 @@ -WITH union_features AS (SELECT - uuid, - event_timestamp, - NULL as {{ featureSet.project }}_{{ featureSet.name }}_v{{ featureSet.version }}_feature_timestamp, - NULL as created_timestamp, - {{ featureSet.entities | join(', ')}}, - true AS is_entity_table -FROM `{{leftTableName}}` -UNION ALL -SELECT - NULL as uuid, - event_timestamp, - event_timestamp as {{ featureSet.project }}_{{ featureSet.name }}_v{{ featureSet.version }}_feature_timestamp, - created_timestamp, - {{ featureSet.entities | join(', ')}}, - false AS is_entity_table -FROM `{{projectId}}.{{datasetId}}.{{ featureSet.project }}_{{ featureSet.name }}_v{{ featureSet.version }}` WHERE event_timestamp <= '{{maxTimestamp}}' AND event_timestamp >= Timestamp_sub(TIMESTAMP '{{ minTimestamp }}', interval {{ featureSet.maxAge }} second) -), joined AS ( -SELECT - uuid, - event_timestamp, - {{ featureSet.entities | join(', ')}}, - {% for featureName in featureSet.features %} - IF(event_timestamp >= {{ featureSet.project }}_{{ featureSet.name }}_v{{ featureSet.version }}_feature_timestamp AND Timestamp_sub(event_timestamp, interval {{ featureSet.maxAge }} second) < {{ featureSet.project }}_{{ featureSet.name }}_v{{ featureSet.version }}_feature_timestamp, {{ featureSet.project }}_{{ featureName }}_v{{ featureSet.version }}, NULL) as {{ featureSet.project }}_{{ featureName }}_v{{ featureSet.version }}{% if loop.last %}{% else %}, {% endif %} - {% endfor %} -FROM ( -SELECT - uuid, - event_timestamp, - {{ featureSet.entities | join(', ')}}, - FIRST_VALUE(created_timestamp IGNORE NULLS) over w AS created_timestamp, - FIRST_VALUE({{ featureSet.project }}_{{ featureSet.name }}_v{{ featureSet.version }}_feature_timestamp IGNORE NULLS) over w AS {{ featureSet.project }}_{{ featureSet.name }}_v{{ featureSet.version }}_feature_timestamp, - is_entity_table -FROM union_features -WINDOW w AS (PARTITION BY {{ featureSet.entities | join(', ') }} ORDER BY event_timestamp DESC, is_entity_table DESC, created_timestamp DESC ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) -) -LEFT JOIN ( -SELECT - event_timestamp as {{ featureSet.project }}_{{ featureSet.name }}_v{{ featureSet.version }}_feature_timestamp, - created_timestamp, - {{ featureSet.entities | join(', ')}}, - {% for featureName in featureSet.features %} - {{ featureName }} as {{ featureSet.project }}_{{ featureName }}_v{{ featureSet.version }}{% if loop.last %}{% else %}, {% endif %} - {% endfor %} -FROM `{{projectId}}.{{datasetId}}.{{ featureSet.project }}_{{ featureSet.name }}_v{{ featureSet.version }}` WHERE event_timestamp <= '{{maxTimestamp}}' AND event_timestamp >= Timestamp_sub(TIMESTAMP '{{ minTimestamp }}', interval {{ featureSet.maxAge }} second) -) USING ({{ featureSet.project }}_{{ featureSet.name }}_v{{ featureSet.version }}_feature_timestamp, created_timestamp, {{ featureSet.entities | join(', ')}}) -WHERE is_entity_table -) -SELECT - k.* -FROM ( - SELECT ARRAY_AGG(row LIMIT 1)[OFFSET(0)] k - FROM joined row - GROUP BY uuid -) \ No newline at end of file diff --git a/serving/src/test/java/feast/serving/controller/ServingServiceGRpcControllerTest.java b/serving/src/test/java/feast/serving/controller/ServingServiceGRpcControllerTest.java index f2c51bc7dde..5c8308daea5 100644 --- a/serving/src/test/java/feast/serving/controller/ServingServiceGRpcControllerTest.java +++ b/serving/src/test/java/feast/serving/controller/ServingServiceGRpcControllerTest.java @@ -19,13 +19,13 @@ import static org.mockito.MockitoAnnotations.initMocks; import com.google.protobuf.Timestamp; -import feast.serving.FeastProperties; -import feast.serving.ServingAPIProto.FeatureReference; -import feast.serving.ServingAPIProto.GetOnlineFeaturesRequest; -import feast.serving.ServingAPIProto.GetOnlineFeaturesRequest.EntityRow; -import feast.serving.ServingAPIProto.GetOnlineFeaturesResponse; +import feast.proto.serving.ServingAPIProto.FeatureReference; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequest; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequest.EntityRow; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse; +import feast.proto.types.ValueProto.Value; +import feast.serving.config.FeastProperties; import feast.serving.service.ServingService; -import feast.types.ValueProto.Value; import io.grpc.StatusRuntimeException; import io.grpc.stub.StreamObserver; import io.jaegertracing.Configuration; @@ -51,18 +51,8 @@ public void setUp() { validRequest = GetOnlineFeaturesRequest.newBuilder() - .addFeatures( - FeatureReference.newBuilder() - .setName("feature1") - .setVersion(1) - .setProject("project") - .build()) - .addFeatures( - FeatureReference.newBuilder() - .setName("feature2") - .setVersion(1) - .setProject("project") - .build()) + .addFeatures(FeatureReference.newBuilder().setName("feature1").build()) + .addFeatures(FeatureReference.newBuilder().setName("feature2").build()) .addEntityRows( EntityRow.newBuilder() .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) diff --git a/serving/src/test/java/feast/serving/service/CachedSpecServiceTest.java b/serving/src/test/java/feast/serving/service/CachedSpecServiceTest.java index abeb44bd731..85f590d5238 100644 --- a/serving/src/test/java/feast/serving/service/CachedSpecServiceTest.java +++ b/serving/src/test/java/feast/serving/service/CachedSpecServiceTest.java @@ -19,34 +19,26 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; -import com.google.common.collect.Lists; -import feast.core.CoreServiceProto.ListFeatureSetsRequest; -import feast.core.CoreServiceProto.ListFeatureSetsResponse; -import feast.core.CoreServiceProto.UpdateStoreRequest; -import feast.core.CoreServiceProto.UpdateStoreResponse; -import feast.core.FeatureSetProto; -import feast.core.FeatureSetProto.FeatureSetSpec; -import feast.core.FeatureSetProto.FeatureSpec; -import feast.core.StoreProto.Store; -import feast.core.StoreProto.Store.RedisConfig; -import feast.core.StoreProto.Store.StoreType; -import feast.core.StoreProto.Store.Subscription; -import feast.serving.ServingAPIProto.FeatureReference; +import feast.proto.core.CoreServiceProto.ListFeatureSetsRequest; +import feast.proto.core.CoreServiceProto.ListFeatureSetsResponse; +import feast.proto.core.FeatureSetProto; +import feast.proto.core.FeatureSetProto.FeatureSetSpec; +import feast.proto.core.FeatureSetProto.FeatureSpec; +import feast.proto.core.StoreProto.Store; +import feast.proto.core.StoreProto.Store.Subscription; +import feast.proto.serving.ServingAPIProto.FeatureReference; +import feast.serving.exception.SpecRetrievalException; import feast.serving.specs.CachedSpecService; import feast.serving.specs.CoreSpecService; -import feast.serving.specs.FeatureSetRequest; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.util.Collections; -import java.util.LinkedHashMap; +import feast.storage.api.retriever.FeatureSetRequest; +import java.util.HashMap; import java.util.List; import java.util.Map; -import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -55,7 +47,6 @@ public class CachedSpecServiceTest { - private File configFile; private Store store; @Rule public final ExpectedException expectedException = ExpectedException.none(); @@ -66,110 +57,68 @@ public class CachedSpecServiceTest { private CachedSpecService cachedSpecService; @Before - public void setUp() throws IOException { + public void setUp() { initMocks(this); - configFile = File.createTempFile("serving", ".yml"); - String yamlString = - "name: SERVING\n" - + "type: REDIS\n" - + "redis_config:\n" - + " host: localhost\n" - + " port: 6379\n" - + "subscriptions:\n" - + "- project: project\n" - + " name: fs1\n" - + " version: \"*\"\n" - + "- project: project\n" - + " name: fs2\n" - + " version: \"*\""; - BufferedWriter writer = new BufferedWriter(new FileWriter(configFile)); - writer.write(yamlString); - writer.close(); - - store = - Store.newBuilder() - .setName("SERVING") - .setType(StoreType.REDIS) - .setRedisConfig(RedisConfig.newBuilder().setHost("localhost").setPort(6379)) - .addSubscriptions( - Subscription.newBuilder() - .setProject("project") - .setName("fs1") - .setVersion("*") - .build()) - .addSubscriptions( - Subscription.newBuilder() - .setProject("project") - .setName("fs2") - .setVersion("*") - .build()) - .build(); + this.store = Store.newBuilder().build(); + this.featureSetSpecs = new HashMap<>(); - when(coreService.updateStore(UpdateStoreRequest.newBuilder().setStore(store).build())) - .thenReturn(UpdateStoreResponse.newBuilder().setStore(store).build()); + this.setupFeatureSetAndStoreSubscription( + "project", + "fs1", + List.of( + FeatureSpec.newBuilder().setName("feature").build(), + FeatureSpec.newBuilder().setName("feature2").build())); - featureSetSpecs = new LinkedHashMap<>(); - featureSetSpecs.put( - "fs1:1", - FeatureSetSpec.newBuilder() - .setProject("project") - .setName("fs1") - .setVersion(1) - .addFeatures(FeatureSpec.newBuilder().setName("feature")) - .build()); - featureSetSpecs.put( - "fs1:2", - FeatureSetSpec.newBuilder() - .setProject("project") - .setName("fs1") - .setVersion(2) - .addFeatures(FeatureSpec.newBuilder().setName("feature")) - .addFeatures(FeatureSpec.newBuilder().setName("feature2")) - .build()); - featureSetSpecs.put( - "fs2:1", + this.setupFeatureSetAndStoreSubscription( + "default", + "fs2", + List.of( + FeatureSpec.newBuilder().setName("feature3").build(), + FeatureSpec.newBuilder().setName("feature4").build(), + FeatureSpec.newBuilder().setName("feature5").build())); + + this.setupFeatureSetAndStoreSubscription( + "default", "fs3", List.of(FeatureSpec.newBuilder().setName("feature4").build())); + + when(this.coreService.registerStore(store)).thenReturn(store); + cachedSpecService = new CachedSpecService(this.coreService, this.store); + } + + private void setupFeatureSetAndStoreSubscription( + String project, String name, List featureSpecs) { + FeatureSetSpec fsSpec = FeatureSetSpec.newBuilder() - .setProject("project") - .setName("fs2") - .setVersion(1) - .addFeatures(FeatureSpec.newBuilder().setName("feature3")) - .build()); - - List fs1FeatureSets = - Lists.newArrayList( - FeatureSetProto.FeatureSet.newBuilder().setSpec(featureSetSpecs.get("fs1:1")).build(), - FeatureSetProto.FeatureSet.newBuilder().setSpec(featureSetSpecs.get("fs1:2")).build()); - List fs2FeatureSets = - Lists.newArrayList( - FeatureSetProto.FeatureSet.newBuilder().setSpec(featureSetSpecs.get("fs2:1")).build()); - when(coreService.listFeatureSets( - ListFeatureSetsRequest.newBuilder() - .setFilter( - ListFeatureSetsRequest.Filter.newBuilder() - .setProject("project") - .setFeatureSetName("fs1") - .setFeatureSetVersion("*") - .build()) - .build())) - .thenReturn(ListFeatureSetsResponse.newBuilder().addAllFeatureSets(fs1FeatureSets).build()); + .setProject(project) + .setName(name) + .addAllFeatures(featureSpecs) + .build(); + this.featureSetSpecs.put(String.format("%s", name), fsSpec); + + this.store = + this.store + .toBuilder() + .addSubscriptions(Subscription.newBuilder().setProject(project).setName(name).build()) + .build(); + + // collect the different versions the featureset with the given name + FeatureSetProto.FeatureSet featureSet = + FeatureSetProto.FeatureSet.newBuilder().setSpec(fsSpec).build(); + when(coreService.listFeatureSets( ListFeatureSetsRequest.newBuilder() .setFilter( ListFeatureSetsRequest.Filter.newBuilder() - .setProject("project") - .setFeatureSetName("fs2") - .setFeatureSetVersion("*") + .setProject(project) + .setFeatureSetName(name) .build()) .build())) - .thenReturn(ListFeatureSetsResponse.newBuilder().addAllFeatureSets(fs2FeatureSets).build()); - - cachedSpecService = new CachedSpecService(coreService, configFile.toPath()); + .thenReturn(ListFeatureSetsResponse.newBuilder().addFeatureSets(featureSet).build()); } - @After - public void tearDown() { - configFile.delete(); + @Test + public void shouldRegisterStoreWithCore() { + verify(coreService, times(1)).registerStore(cachedSpecService.getStore()); } @Test @@ -181,81 +130,96 @@ public void shouldPopulateAndReturnStore() { @Test public void shouldPopulateAndReturnFeatureSets() { + // test that CachedSpecService can retrieve fully qualified feature references. cachedSpecService.populateCache(); - FeatureReference frv1 = + FeatureReference fs1fr1 = FeatureReference.newBuilder() .setProject("project") .setName("feature") - .setVersion(1) + .setFeatureSet("fs1") .build(); - FeatureReference frv2 = + FeatureReference fs1fr2 = FeatureReference.newBuilder() .setProject("project") - .setName("feature") - .setVersion(2) + .setName("feature2") + .setFeatureSet("fs1") .build(); assertThat( - cachedSpecService.getFeatureSets(Collections.singletonList(frv1)), + cachedSpecService.getFeatureSets(List.of(fs1fr1, fs1fr2)), equalTo( - Lists.newArrayList( + List.of( FeatureSetRequest.newBuilder() - .addFeatureReference(frv1) - .setSpec(featureSetSpecs.get("fs1:1")) + .addFeatureReference(fs1fr1) + .addFeatureReference(fs1fr2) + .setSpec(featureSetSpecs.get("fs1")) .build()))); + } + + @Test + public void shouldPopulateAndReturnFeatureSetWithDefaultProjectIfProjectNotSupplied() { + // test that CachedSpecService will use default project when project unspecified + FeatureReference fs2fr3 = + FeatureReference.newBuilder().setName("feature3").setFeatureSet("fs2").build(); + // check that this is true for references in where feature set is unspecified + FeatureReference fs2fr5 = FeatureReference.newBuilder().setName("feature5").build(); + assertThat( - cachedSpecService.getFeatureSets(Collections.singletonList(frv2)), + cachedSpecService.getFeatureSets(List.of(fs2fr3, fs2fr5)), equalTo( - Lists.newArrayList( + List.of( FeatureSetRequest.newBuilder() - .addFeatureReference(frv2) - .setSpec(featureSetSpecs.get("fs1:2")) + .addFeatureReference(fs2fr3) + .addFeatureReference(fs2fr5) + .setSpec(featureSetSpecs.get("fs2")) .build()))); } @Test - public void shouldPopulateAndReturnLatestFeatureSetIfVersionsNotSupplied() { - cachedSpecService.populateCache(); - FeatureReference frv1 = + public void shouldPopulateAndReturnClosestFeatureSetIfFeatureSetNotSupplied() { + // test that CachedSpecService will try to match a featureset without a featureset name in + // reference + FeatureReference fs1fr1 = FeatureReference.newBuilder().setProject("project").setName("feature").build(); + // check that this is true for reference in which project is unspecified + FeatureReference fs2fr3 = FeatureReference.newBuilder().setName("feature3").build(); + assertThat( - cachedSpecService.getFeatureSets(Collections.singletonList(frv1)), - equalTo( - Lists.newArrayList( - FeatureSetRequest.newBuilder() - .addFeatureReference(frv1) - .setSpec(featureSetSpecs.get("fs1:2")) - .build()))); + cachedSpecService.getFeatureSets(List.of(fs1fr1, fs2fr3)), + containsInAnyOrder( + List.of( + FeatureSetRequest.newBuilder() + .addFeatureReference(fs1fr1) + .setSpec(featureSetSpecs.get("fs1")) + .build(), + FeatureSetRequest.newBuilder() + .addFeatureReference(fs2fr3) + .setSpec(featureSetSpecs.get("fs2")) + .build()) + .toArray())); } @Test public void shouldPopulateAndReturnFeatureSetsGivenFeaturesFromDifferentFeatureSets() { cachedSpecService.populateCache(); - FeatureReference frv1 = - FeatureReference.newBuilder() - .setProject("project") - .setName("feature") - .setVersion(1) - .build(); - FeatureReference fr3 = - FeatureReference.newBuilder() - .setProject("project") - .setName("feature3") - .setVersion(1) - .build(); + FeatureReference fs1fr1 = + FeatureReference.newBuilder().setProject("project").setName("feature").build(); + + FeatureReference fs2fr3 = + FeatureReference.newBuilder().setProject("default").setName("feature3").build(); assertThat( - cachedSpecService.getFeatureSets(Lists.newArrayList(frv1, fr3)), + cachedSpecService.getFeatureSets(List.of(fs1fr1, fs2fr3)), containsInAnyOrder( - Lists.newArrayList( + List.of( FeatureSetRequest.newBuilder() - .addFeatureReference(frv1) - .setSpec(featureSetSpecs.get("fs1:1")) + .addFeatureReference(fs1fr1) + .setSpec(featureSetSpecs.get("fs1")) .build(), FeatureSetRequest.newBuilder() - .addFeatureReference(fr3) - .setSpec(featureSetSpecs.get("fs2:1")) + .addFeatureReference(fs2fr3) + .setSpec(featureSetSpecs.get("fs2")) .build()) .toArray())); } @@ -264,26 +228,31 @@ public void shouldPopulateAndReturnFeatureSetsGivenFeaturesFromDifferentFeatureS public void shouldPopulateAndReturnFeatureSetGivenFeaturesFromSameFeatureSet() { cachedSpecService.populateCache(); FeatureReference fr1 = - FeatureReference.newBuilder() - .setProject("project") - .setName("feature") - .setVersion(2) - .build(); + FeatureReference.newBuilder().setProject("project").setName("feature").build(); FeatureReference fr2 = - FeatureReference.newBuilder() - .setProject("project") - .setName("feature2") - .setVersion(2) - .build(); + FeatureReference.newBuilder().setProject("project").setName("feature2").build(); assertThat( - cachedSpecService.getFeatureSets(Lists.newArrayList(fr1, fr2)), + cachedSpecService.getFeatureSets(List.of(fr1, fr2)), equalTo( - Lists.newArrayList( + List.of( FeatureSetRequest.newBuilder() .addFeatureReference(fr1) .addFeatureReference(fr2) - .setSpec(featureSetSpecs.get("fs1:2")) + .setSpec(featureSetSpecs.get("fs1")) .build()))); } + + @Test + public void shouldThrowExceptionWhenMultipleFeatureSetMapToFeatureReference() + throws SpecRetrievalException { + // both fs2 and fs3 have the feature with the same name. + // using a generic feature reference only specifying the feature's name + // should cause a multiple feature sets to match and throw an error + FeatureReference fs2fr4 = FeatureReference.newBuilder().setName("feature4").build(); + FeatureReference fs3fr4 = FeatureReference.newBuilder().setName("feature4").build(); + + expectedException.expect(SpecRetrievalException.class); + cachedSpecService.getFeatureSets(List.of(fs2fr4, fs3fr4)); + } } diff --git a/serving/src/test/java/feast/serving/service/OnlineServingServiceTest.java b/serving/src/test/java/feast/serving/service/OnlineServingServiceTest.java new file mode 100644 index 00000000000..6358460a070 --- /dev/null +++ b/serving/src/test/java/feast/serving/service/OnlineServingServiceTest.java @@ -0,0 +1,380 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2019 The Feast Authors + * + * 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 feast.serving.service; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; + +import com.google.common.collect.Lists; +import com.google.protobuf.Duration; +import com.google.protobuf.Timestamp; +import feast.proto.core.FeatureSetProto.EntitySpec; +import feast.proto.core.FeatureSetProto.FeatureSetSpec; +import feast.proto.serving.ServingAPIProto.FeatureReference; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequest; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequest.EntityRow; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse.FieldValues; +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.proto.types.FieldProto.Field; +import feast.proto.types.ValueProto.Value; +import feast.serving.specs.CachedSpecService; +import feast.storage.api.retriever.FeatureSetRequest; +import feast.storage.connectors.redis.retriever.RedisOnlineRetriever; +import io.opentracing.Tracer; +import io.opentracing.Tracer.SpanBuilder; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.Mockito; + +public class OnlineServingServiceTest { + + @Mock CachedSpecService specService; + + @Mock Tracer tracer; + + @Mock RedisOnlineRetriever retriever; + + private OnlineServingService onlineServingService; + + @Before + public void setUp() { + initMocks(this); + onlineServingService = new OnlineServingService(retriever, specService, tracer); + } + + @Test + public void shouldReturnResponseWithValuesIfKeysPresent() { + GetOnlineFeaturesRequest request = + GetOnlineFeaturesRequest.newBuilder() + .addFeatures(FeatureReference.newBuilder().setName("feature1").build()) + .addFeatures(FeatureReference.newBuilder().setName("feature2").build()) + .addEntityRows( + EntityRow.newBuilder() + .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) + .putFields("entity1", intValue(1)) + .putFields("entity2", strValue("a"))) + .addEntityRows( + EntityRow.newBuilder() + .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) + .putFields("entity1", intValue(2)) + .putFields("entity2", strValue("b"))) + .build(); + + List featureRows = + Lists.newArrayList( + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setSeconds(100)) + .addAllFields( + Lists.newArrayList( + Field.newBuilder().setName("entity1").setValue(intValue(1)).build(), + Field.newBuilder().setName("entity2").setValue(strValue("a")).build(), + Field.newBuilder().setName("feature1").setValue(intValue(1)).build(), + Field.newBuilder().setName("feature2").setValue(intValue(1)).build())) + .build(), + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setSeconds(100)) + .addAllFields( + Lists.newArrayList( + Field.newBuilder().setName("entity1").setValue(intValue(2)).build(), + Field.newBuilder().setName("entity2").setValue(strValue("b")).build(), + Field.newBuilder().setName("feature1").setValue(intValue(2)).build(), + Field.newBuilder().setName("feature2").setValue(intValue(2)).build())) + .build()); + + FeatureSetRequest featureSetRequest = + FeatureSetRequest.newBuilder() + .addAllFeatureReferences(request.getFeaturesList()) + .setSpec(getFeatureSetSpec()) + .build(); + + when(specService.getFeatureSets(request.getFeaturesList())) + .thenReturn(Collections.singletonList(featureSetRequest)); + when(retriever.getOnlineFeatures( + request.getEntityRowsList(), Collections.singletonList(featureSetRequest))) + .thenReturn(Collections.singletonList(featureRows)); + when(tracer.buildSpan(ArgumentMatchers.any())).thenReturn(Mockito.mock(SpanBuilder.class)); + + GetOnlineFeaturesResponse expected = + GetOnlineFeaturesResponse.newBuilder() + .addFieldValues( + FieldValues.newBuilder() + .putFields("entity1", intValue(1)) + .putFields("entity2", strValue("a")) + .putFields("feature1", intValue(1)) + .putFields("feature2", intValue(1))) + .addFieldValues( + FieldValues.newBuilder() + .putFields("entity1", intValue(2)) + .putFields("entity2", strValue("b")) + .putFields("feature1", intValue(2)) + .putFields("feature2", intValue(2))) + .build(); + GetOnlineFeaturesResponse actual = onlineServingService.getOnlineFeatures(request); + assertThat( + responseToMapList(actual), containsInAnyOrder(responseToMapList(expected).toArray())); + } + + @Test + public void shouldReturnResponseWithUnsetValuesIfKeysNotPresent() { + // some keys not present, should have empty values + GetOnlineFeaturesRequest request = + GetOnlineFeaturesRequest.newBuilder() + .addFeatures(FeatureReference.newBuilder().setName("feature1").build()) + .addFeatures(FeatureReference.newBuilder().setName("feature2").build()) + .addEntityRows( + EntityRow.newBuilder() + .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) + .putFields("entity1", intValue(1)) + .putFields("entity2", strValue("a"))) + .addEntityRows( + EntityRow.newBuilder() + .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) + .putFields("entity1", intValue(2)) + .putFields("entity2", strValue("b"))) + .build(); + + FeatureSetRequest featureSetRequest = + FeatureSetRequest.newBuilder() + .addAllFeatureReferences(request.getFeaturesList()) + .setSpec(getFeatureSetSpec()) + .build(); + + List featureRows = + Lists.newArrayList( + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setSeconds(100)) + .setFeatureSet("project/featureSet") + .addAllFields( + Lists.newArrayList( + Field.newBuilder().setName("feature1").setValue(intValue(1)).build(), + Field.newBuilder().setName("feature2").setValue(intValue(1)).build())) + .build(), + FeatureRow.newBuilder() + .setFeatureSet("project/featureSet") + .addAllFields( + Lists.newArrayList( + Field.newBuilder().setName("feature1").build(), + Field.newBuilder().setName("feature2").build())) + .build()); + + when(specService.getFeatureSets(request.getFeaturesList())) + .thenReturn(Collections.singletonList(featureSetRequest)); + when(retriever.getOnlineFeatures( + request.getEntityRowsList(), Collections.singletonList(featureSetRequest))) + .thenReturn(Collections.singletonList(featureRows)); + when(tracer.buildSpan(ArgumentMatchers.any())).thenReturn(Mockito.mock(SpanBuilder.class)); + + GetOnlineFeaturesResponse expected = + GetOnlineFeaturesResponse.newBuilder() + .addFieldValues( + FieldValues.newBuilder() + .putFields("entity1", intValue(1)) + .putFields("entity2", strValue("a")) + .putFields("feature1", intValue(1)) + .putFields("feature2", intValue(1))) + .addFieldValues( + FieldValues.newBuilder() + .putFields("entity1", intValue(2)) + .putFields("entity2", strValue("b")) + .putFields("feature1", Value.newBuilder().build()) + .putFields("feature2", Value.newBuilder().build())) + .build(); + GetOnlineFeaturesResponse actual = onlineServingService.getOnlineFeatures(request); + assertThat( + responseToMapList(actual), containsInAnyOrder(responseToMapList(expected).toArray())); + } + + @Test + public void shouldReturnResponseWithUnsetValuesIfMaxAgeIsExceeded() { + // keys present, but too stale comp. to maxAge + GetOnlineFeaturesRequest request = + GetOnlineFeaturesRequest.newBuilder() + .addFeatures(FeatureReference.newBuilder().setName("feature1").build()) + .addFeatures(FeatureReference.newBuilder().setName("feature2").build()) + .addEntityRows( + EntityRow.newBuilder() + .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) + .putFields("entity1", intValue(1)) + .putFields("entity2", strValue("a"))) + .addEntityRows( + EntityRow.newBuilder() + .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) + .putFields("entity1", intValue(2)) + .putFields("entity2", strValue("b"))) + .build(); + + List featureRows = + Lists.newArrayList( + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setSeconds(100)) + .addAllFields( + Lists.newArrayList( + Field.newBuilder().setName("entity1").setValue(intValue(1)).build(), + Field.newBuilder().setName("entity2").setValue(strValue("a")).build(), + Field.newBuilder().setName("feature1").setValue(intValue(1)).build(), + Field.newBuilder().setName("feature2").setValue(intValue(1)).build())) + .setFeatureSet("project/featureSet") + .build(), + FeatureRow.newBuilder() + .setEventTimestamp( + Timestamp.newBuilder().setSeconds(50)) // this value should be nulled + .addAllFields( + Lists.newArrayList( + Field.newBuilder().setName("entity1").setValue(intValue(2)).build(), + Field.newBuilder().setName("entity2").setValue(strValue("b")).build(), + Field.newBuilder().setName("feature1").setValue(intValue(2)).build(), + Field.newBuilder().setName("feature2").setValue(intValue(2)).build())) + .setFeatureSet("project/featureSet") + .build()); + + FeatureSetSpec spec = + getFeatureSetSpec().toBuilder().setMaxAge(Duration.newBuilder().setSeconds(1)).build(); + FeatureSetRequest featureSetRequest = + FeatureSetRequest.newBuilder() + .addAllFeatureReferences(request.getFeaturesList()) + .setSpec(spec) + .build(); + + when(specService.getFeatureSets(request.getFeaturesList())) + .thenReturn(Collections.singletonList(featureSetRequest)); + when(retriever.getOnlineFeatures( + request.getEntityRowsList(), Collections.singletonList(featureSetRequest))) + .thenReturn(Collections.singletonList(featureRows)); + when(tracer.buildSpan(ArgumentMatchers.any())).thenReturn(Mockito.mock(SpanBuilder.class)); + + GetOnlineFeaturesResponse expected = + GetOnlineFeaturesResponse.newBuilder() + .addFieldValues( + FieldValues.newBuilder() + .putFields("entity1", intValue(1)) + .putFields("entity2", strValue("a")) + .putFields("feature1", intValue(1)) + .putFields("feature2", intValue(1))) + .addFieldValues( + FieldValues.newBuilder() + .putFields("entity1", intValue(2)) + .putFields("entity2", strValue("b")) + .putFields("feature1", Value.newBuilder().build()) + .putFields("feature2", Value.newBuilder().build())) + .build(); + GetOnlineFeaturesResponse actual = onlineServingService.getOnlineFeatures(request); + assertThat( + responseToMapList(actual), containsInAnyOrder(responseToMapList(expected).toArray())); + } + + @Test + public void shouldFilterOutUndesiredRows() { + // requested rows less than the rows available in the featureset + GetOnlineFeaturesRequest request = + GetOnlineFeaturesRequest.newBuilder() + .addFeatures(FeatureReference.newBuilder().setName("feature1").build()) + .addEntityRows( + EntityRow.newBuilder() + .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) + .putFields("entity1", intValue(1)) + .putFields("entity2", strValue("a"))) + .addEntityRows( + EntityRow.newBuilder() + .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) + .putFields("entity1", intValue(2)) + .putFields("entity2", strValue("b"))) + .build(); + + List featureRows = + Lists.newArrayList( + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setSeconds(100)) + .addAllFields( + Lists.newArrayList( + Field.newBuilder().setName("entity1").setValue(intValue(1)).build(), + Field.newBuilder().setName("entity2").setValue(strValue("a")).build(), + Field.newBuilder().setName("feature1").setValue(intValue(1)).build(), + Field.newBuilder().setName("feature2").setValue(intValue(1)).build())) + .build(), + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setSeconds(100)) + .addAllFields( + Lists.newArrayList( + Field.newBuilder().setName("entity1").setValue(intValue(2)).build(), + Field.newBuilder().setName("entity2").setValue(strValue("b")).build(), + Field.newBuilder().setName("feature1").setValue(intValue(2)).build(), + Field.newBuilder().setName("feature2").setValue(intValue(2)).build())) + .build()); + + FeatureSetRequest featureSetRequest = + FeatureSetRequest.newBuilder() + .addAllFeatureReferences(request.getFeaturesList()) + .setSpec(getFeatureSetSpec()) + .build(); + + when(specService.getFeatureSets(request.getFeaturesList())) + .thenReturn(Collections.singletonList(featureSetRequest)); + when(retriever.getOnlineFeatures( + request.getEntityRowsList(), Collections.singletonList(featureSetRequest))) + .thenReturn(Collections.singletonList(featureRows)); + when(tracer.buildSpan(ArgumentMatchers.any())).thenReturn(Mockito.mock(SpanBuilder.class)); + + GetOnlineFeaturesResponse expected = + GetOnlineFeaturesResponse.newBuilder() + .addFieldValues( + FieldValues.newBuilder() + .putFields("entity1", intValue(1)) + .putFields("entity2", strValue("a")) + .putFields("feature1", intValue(1))) + .addFieldValues( + FieldValues.newBuilder() + .putFields("entity1", intValue(2)) + .putFields("entity2", strValue("b")) + .putFields("feature1", intValue(2))) + .build(); + GetOnlineFeaturesResponse actual = onlineServingService.getOnlineFeatures(request); + assertThat( + responseToMapList(actual), containsInAnyOrder(responseToMapList(expected).toArray())); + } + + private List> responseToMapList(GetOnlineFeaturesResponse response) { + return response.getFieldValuesList().stream() + .map(FieldValues::getFieldsMap) + .collect(Collectors.toList()); + } + + private Value intValue(int val) { + return Value.newBuilder().setInt64Val(val).build(); + } + + private Value strValue(String val) { + return Value.newBuilder().setStringVal(val).build(); + } + + private FeatureSetSpec getFeatureSetSpec() { + return FeatureSetSpec.newBuilder() + .setName("featureSet") + .addEntities(EntitySpec.newBuilder().setName("entity1")) + .addEntities(EntitySpec.newBuilder().setName("entity2")) + .setMaxAge(Duration.newBuilder().setSeconds(30)) // default + .build(); + } +} diff --git a/serving/src/test/java/feast/serving/service/RedisBackedJobServiceTest.java b/serving/src/test/java/feast/serving/service/RedisBackedJobServiceTest.java new file mode 100644 index 00000000000..23626c2cb85 --- /dev/null +++ b/serving/src/test/java/feast/serving/service/RedisBackedJobServiceTest.java @@ -0,0 +1,60 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.serving.service; + +import io.lettuce.core.RedisClient; +import io.lettuce.core.RedisURI; +import io.lettuce.core.codec.ByteArrayCodec; +import java.io.IOException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import redis.embedded.RedisServer; + +public class RedisBackedJobServiceTest { + + private static Integer REDIS_PORT = 51235; + private RedisServer redis; + + @Before + public void setUp() throws IOException { + redis = new RedisServer(REDIS_PORT); + redis.start(); + } + + @After + public void teardown() { + redis.stop(); + } + + @Test + public void shouldRecoverIfRedisConnectionIsLost() { + RedisClient client = RedisClient.create(RedisURI.create("localhost", REDIS_PORT)); + RedisBackedJobService jobService = + new RedisBackedJobService(client.connect(new ByteArrayCodec())); + jobService.get("does not exist"); + redis.stop(); + try { + jobService.get("does not exist"); + } catch (Exception e) { + // pass, this should fail, and return a broken connection to the pool + } + redis.start(); + jobService.get("does not exist"); + client.shutdown(); + } +} diff --git a/serving/src/test/java/feast/serving/service/RedisServingServiceTest.java b/serving/src/test/java/feast/serving/service/RedisServingServiceTest.java deleted file mode 100644 index 042107e1177..00000000000 --- a/serving/src/test/java/feast/serving/service/RedisServingServiceTest.java +++ /dev/null @@ -1,633 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright 2018-2019 The Feast Authors - * - * 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 feast.serving.service; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.mockito.Mockito.when; -import static org.mockito.MockitoAnnotations.initMocks; - -import com.google.common.collect.Lists; -import com.google.protobuf.AbstractMessageLite; -import com.google.protobuf.Duration; -import com.google.protobuf.Timestamp; -import feast.core.FeatureSetProto.EntitySpec; -import feast.core.FeatureSetProto.FeatureSetSpec; -import feast.serving.ServingAPIProto.FeatureReference; -import feast.serving.ServingAPIProto.GetOnlineFeaturesRequest; -import feast.serving.ServingAPIProto.GetOnlineFeaturesRequest.EntityRow; -import feast.serving.ServingAPIProto.GetOnlineFeaturesResponse; -import feast.serving.ServingAPIProto.GetOnlineFeaturesResponse.FieldValues; -import feast.serving.specs.CachedSpecService; -import feast.serving.specs.FeatureSetRequest; -import feast.storage.RedisProto.RedisKey; -import feast.types.FeatureRowProto.FeatureRow; -import feast.types.FieldProto.Field; -import feast.types.ValueProto.Value; -import io.opentracing.Tracer; -import io.opentracing.Tracer.SpanBuilder; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentMatchers; -import org.mockito.Mock; -import org.mockito.Mockito; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisPool; - -public class RedisServingServiceTest { - - @Mock JedisPool jedisPool; - - @Mock Jedis jedis; - - @Mock CachedSpecService specService; - - @Mock Tracer tracer; - - private RedisServingService redisServingService; - private byte[][] redisKeyList; - - @Before - public void setUp() { - initMocks(this); - - redisServingService = new RedisServingService(jedisPool, specService, tracer); - redisKeyList = - Lists.newArrayList( - RedisKey.newBuilder() - .setFeatureSet("project/featureSet:1") - .addAllEntities( - Lists.newArrayList( - Field.newBuilder().setName("entity1").setValue(intValue(1)).build(), - Field.newBuilder().setName("entity2").setValue(strValue("a")).build())) - .build(), - RedisKey.newBuilder() - .setFeatureSet("project/featureSet:1") - .addAllEntities( - Lists.newArrayList( - Field.newBuilder().setName("entity1").setValue(intValue(2)).build(), - Field.newBuilder().setName("entity2").setValue(strValue("b")).build())) - .build()) - .stream() - .map(AbstractMessageLite::toByteArray) - .collect(Collectors.toList()) - .toArray(new byte[0][0]); - } - - @Test - public void shouldReturnResponseWithValuesIfKeysPresent() { - GetOnlineFeaturesRequest request = - GetOnlineFeaturesRequest.newBuilder() - .addFeatures( - FeatureReference.newBuilder() - .setName("feature1") - .setVersion(1) - .setProject("project") - .build()) - .addFeatures( - FeatureReference.newBuilder() - .setName("feature2") - .setVersion(1) - .setProject("project") - .build()) - .addEntityRows( - EntityRow.newBuilder() - .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) - .putFields("entity1", intValue(1)) - .putFields("entity2", strValue("a"))) - .addEntityRows( - EntityRow.newBuilder() - .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) - .putFields("entity1", intValue(2)) - .putFields("entity2", strValue("b"))) - .build(); - - List featureRows = - Lists.newArrayList( - FeatureRow.newBuilder() - .setEventTimestamp(Timestamp.newBuilder().setSeconds(100)) - .addAllFields( - Lists.newArrayList( - Field.newBuilder().setName("entity1").setValue(intValue(1)).build(), - Field.newBuilder().setName("entity2").setValue(strValue("a")).build(), - Field.newBuilder().setName("feature1").setValue(intValue(1)).build(), - Field.newBuilder().setName("feature2").setValue(intValue(1)).build())) - .setFeatureSet("featureSet:1") - .build(), - FeatureRow.newBuilder() - .setEventTimestamp(Timestamp.newBuilder().setSeconds(100)) - .addAllFields( - Lists.newArrayList( - Field.newBuilder().setName("entity1").setValue(intValue(2)).build(), - Field.newBuilder().setName("entity2").setValue(strValue("b")).build(), - Field.newBuilder().setName("feature1").setValue(intValue(2)).build(), - Field.newBuilder().setName("feature2").setValue(intValue(2)).build())) - .setFeatureSet("featureSet:1") - .build()); - - FeatureSetRequest featureSetRequest = - FeatureSetRequest.newBuilder() - .addAllFeatureReferences(request.getFeaturesList()) - .setSpec(getFeatureSetSpec()) - .build(); - - List featureRowBytes = - featureRows.stream().map(AbstractMessageLite::toByteArray).collect(Collectors.toList()); - when(specService.getFeatureSets(request.getFeaturesList())) - .thenReturn(Collections.singletonList(featureSetRequest)); - when(jedisPool.getResource()).thenReturn(jedis); - when(jedis.mget(redisKeyList)).thenReturn(featureRowBytes); - when(tracer.buildSpan(ArgumentMatchers.any())).thenReturn(Mockito.mock(SpanBuilder.class)); - - GetOnlineFeaturesResponse expected = - GetOnlineFeaturesResponse.newBuilder() - .addFieldValues( - FieldValues.newBuilder() - .putFields("entity1", intValue(1)) - .putFields("entity2", strValue("a")) - .putFields("project/feature1:1", intValue(1)) - .putFields("project/feature2:1", intValue(1))) - .addFieldValues( - FieldValues.newBuilder() - .putFields("entity1", intValue(2)) - .putFields("entity2", strValue("b")) - .putFields("project/feature1:1", intValue(2)) - .putFields("project/feature2:1", intValue(2))) - .build(); - GetOnlineFeaturesResponse actual = redisServingService.getOnlineFeatures(request); - assertThat( - responseToMapList(actual), containsInAnyOrder(responseToMapList(expected).toArray())); - } - - @Test - public void shouldReturnResponseWithValuesWhenFeatureSetSpecHasUnspecifiedMaxAge() { - GetOnlineFeaturesRequest request = - GetOnlineFeaturesRequest.newBuilder() - .addFeatures( - FeatureReference.newBuilder() - .setName("feature1") - .setVersion(1) - .setProject("project") - .build()) - .addFeatures( - FeatureReference.newBuilder() - .setName("feature2") - .setVersion(1) - .setProject("project") - .build()) - .addEntityRows( - EntityRow.newBuilder() - .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) - .putFields("entity1", intValue(1)) - .putFields("entity2", strValue("a"))) - .addEntityRows( - EntityRow.newBuilder() - .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) - .putFields("entity1", intValue(2)) - .putFields("entity2", strValue("b"))) - .build(); - - List featureRows = - Lists.newArrayList( - FeatureRow.newBuilder() - .setEventTimestamp(Timestamp.newBuilder().setSeconds(2)) // much older timestamp - .addAllFields( - Lists.newArrayList( - Field.newBuilder().setName("entity1").setValue(intValue(1)).build(), - Field.newBuilder().setName("entity2").setValue(strValue("a")).build(), - Field.newBuilder().setName("feature1").setValue(intValue(1)).build(), - Field.newBuilder().setName("feature2").setValue(intValue(1)).build())) - .setFeatureSet("featureSet:1") - .build(), - FeatureRow.newBuilder() - .setEventTimestamp(Timestamp.newBuilder().setSeconds(15)) // much older timestamp - .addAllFields( - Lists.newArrayList( - Field.newBuilder().setName("entity1").setValue(intValue(2)).build(), - Field.newBuilder().setName("entity2").setValue(strValue("b")).build(), - Field.newBuilder().setName("feature1").setValue(intValue(2)).build(), - Field.newBuilder().setName("feature2").setValue(intValue(2)).build())) - .setFeatureSet("featureSet:1") - .build()); - - FeatureSetRequest featureSetRequest = - FeatureSetRequest.newBuilder() - .addAllFeatureReferences(request.getFeaturesList()) - .setSpec(getFeatureSetSpecWithNoMaxAge()) - .build(); - - List featureRowBytes = - featureRows.stream().map(AbstractMessageLite::toByteArray).collect(Collectors.toList()); - when(specService.getFeatureSets(request.getFeaturesList())) - .thenReturn(Collections.singletonList(featureSetRequest)); - when(jedisPool.getResource()).thenReturn(jedis); - when(jedis.mget(redisKeyList)).thenReturn(featureRowBytes); - when(tracer.buildSpan(ArgumentMatchers.any())).thenReturn(Mockito.mock(SpanBuilder.class)); - - GetOnlineFeaturesResponse expected = - GetOnlineFeaturesResponse.newBuilder() - .addFieldValues( - FieldValues.newBuilder() - .putFields("entity1", intValue(1)) - .putFields("entity2", strValue("a")) - .putFields("project/feature1:1", intValue(1)) - .putFields("project/feature2:1", intValue(1))) - .addFieldValues( - FieldValues.newBuilder() - .putFields("entity1", intValue(2)) - .putFields("entity2", strValue("b")) - .putFields("project/feature1:1", intValue(2)) - .putFields("project/feature2:1", intValue(2))) - .build(); - GetOnlineFeaturesResponse actual = redisServingService.getOnlineFeatures(request); - assertThat( - responseToMapList(actual), containsInAnyOrder(responseToMapList(expected).toArray())); - } - - @Test - public void shouldReturnKeysWithoutVersionifNotProvided() { - GetOnlineFeaturesRequest request = - GetOnlineFeaturesRequest.newBuilder() - .addFeatures( - FeatureReference.newBuilder() - .setName("feature1") - .setVersion(1) - .setProject("project") - .build()) - .addFeatures( - FeatureReference.newBuilder().setName("feature2").setProject("project").build()) - .addEntityRows( - EntityRow.newBuilder() - .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) - .putFields("entity1", intValue(1)) - .putFields("entity2", strValue("a"))) - .addEntityRows( - EntityRow.newBuilder() - .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) - .putFields("entity1", intValue(2)) - .putFields("entity2", strValue("b"))) - .build(); - - List featureRows = - Lists.newArrayList( - FeatureRow.newBuilder() - .setEventTimestamp(Timestamp.newBuilder().setSeconds(100)) - .addAllFields( - Lists.newArrayList( - Field.newBuilder().setName("entity1").setValue(intValue(1)).build(), - Field.newBuilder().setName("entity2").setValue(strValue("a")).build(), - Field.newBuilder().setName("feature1").setValue(intValue(1)).build(), - Field.newBuilder().setName("feature2").setValue(intValue(1)).build())) - .setFeatureSet("featureSet:1") - .build(), - FeatureRow.newBuilder() - .setEventTimestamp(Timestamp.newBuilder().setSeconds(100)) - .addAllFields( - Lists.newArrayList( - Field.newBuilder().setName("entity1").setValue(intValue(2)).build(), - Field.newBuilder().setName("entity2").setValue(strValue("b")).build(), - Field.newBuilder().setName("feature1").setValue(intValue(2)).build(), - Field.newBuilder().setName("feature2").setValue(intValue(2)).build())) - .setFeatureSet("featureSet:1") - .build()); - - FeatureSetRequest featureSetRequest = - FeatureSetRequest.newBuilder() - .addAllFeatureReferences(request.getFeaturesList()) - .setSpec(getFeatureSetSpec()) - .build(); - - List featureRowBytes = - featureRows.stream().map(AbstractMessageLite::toByteArray).collect(Collectors.toList()); - when(specService.getFeatureSets(request.getFeaturesList())) - .thenReturn(Collections.singletonList(featureSetRequest)); - when(jedisPool.getResource()).thenReturn(jedis); - when(jedis.mget(redisKeyList)).thenReturn(featureRowBytes); - when(tracer.buildSpan(ArgumentMatchers.any())).thenReturn(Mockito.mock(SpanBuilder.class)); - - GetOnlineFeaturesResponse expected = - GetOnlineFeaturesResponse.newBuilder() - .addFieldValues( - FieldValues.newBuilder() - .putFields("entity1", intValue(1)) - .putFields("entity2", strValue("a")) - .putFields("project/feature1:1", intValue(1)) - .putFields("project/feature2", intValue(1))) - .addFieldValues( - FieldValues.newBuilder() - .putFields("entity1", intValue(2)) - .putFields("entity2", strValue("b")) - .putFields("project/feature1:1", intValue(2)) - .putFields("project/feature2", intValue(2))) - .build(); - GetOnlineFeaturesResponse actual = redisServingService.getOnlineFeatures(request); - assertThat( - responseToMapList(actual), containsInAnyOrder(responseToMapList(expected).toArray())); - } - - @Test - public void shouldReturnResponseWithUnsetValuesIfKeysNotPresent() { - // some keys not present, should have empty values - GetOnlineFeaturesRequest request = - GetOnlineFeaturesRequest.newBuilder() - .addFeatures( - FeatureReference.newBuilder() - .setName("feature1") - .setVersion(1) - .setProject("project") - .build()) - .addFeatures( - FeatureReference.newBuilder() - .setName("feature2") - .setVersion(1) - .setProject("project") - .build()) - .addEntityRows( - EntityRow.newBuilder() - .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) - .putFields("entity1", intValue(1)) - .putFields("entity2", strValue("a"))) - .addEntityRows( - EntityRow.newBuilder() - .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) - .putFields("entity1", intValue(2)) - .putFields("entity2", strValue("b"))) - .build(); - - List featureRows = - Lists.newArrayList( - FeatureRow.newBuilder() - .setEventTimestamp(Timestamp.newBuilder().setSeconds(100)) - .addAllFields( - Lists.newArrayList( - Field.newBuilder().setName("entity1").setValue(intValue(1)).build(), - Field.newBuilder().setName("entity2").setValue(strValue("a")).build(), - Field.newBuilder().setName("feature1").setValue(intValue(1)).build(), - Field.newBuilder().setName("feature2").setValue(intValue(1)).build())) - .setFeatureSet("featureSet:1") - .build(), - FeatureRow.newBuilder() - .setEventTimestamp(Timestamp.newBuilder()) - .addAllFields( - Lists.newArrayList( - Field.newBuilder().setName("entity1").setValue(intValue(2)).build(), - Field.newBuilder().setName("entity2").setValue(strValue("b")).build(), - Field.newBuilder().setName("feature1").build(), - Field.newBuilder().setName("feature2").build())) - .setFeatureSet("featureSet:1") - .build()); - - FeatureSetRequest featureSetRequest = - FeatureSetRequest.newBuilder() - .addAllFeatureReferences(request.getFeaturesList()) - .setSpec(getFeatureSetSpec()) - .build(); - - List featureRowBytes = Lists.newArrayList(featureRows.get(0).toByteArray(), null); - when(specService.getFeatureSets(request.getFeaturesList())) - .thenReturn(Collections.singletonList(featureSetRequest)); - when(jedisPool.getResource()).thenReturn(jedis); - when(jedis.mget(redisKeyList)).thenReturn(featureRowBytes); - when(tracer.buildSpan(ArgumentMatchers.any())).thenReturn(Mockito.mock(SpanBuilder.class)); - - GetOnlineFeaturesResponse expected = - GetOnlineFeaturesResponse.newBuilder() - .addFieldValues( - FieldValues.newBuilder() - .putFields("entity1", intValue(1)) - .putFields("entity2", strValue("a")) - .putFields("project/feature1:1", intValue(1)) - .putFields("project/feature2:1", intValue(1))) - .addFieldValues( - FieldValues.newBuilder() - .putFields("entity1", intValue(2)) - .putFields("entity2", strValue("b")) - .putFields("project/feature1:1", Value.newBuilder().build()) - .putFields("project/feature2:1", Value.newBuilder().build())) - .build(); - GetOnlineFeaturesResponse actual = redisServingService.getOnlineFeatures(request); - assertThat( - responseToMapList(actual), containsInAnyOrder(responseToMapList(expected).toArray())); - } - - @Test - public void shouldReturnResponseWithUnsetValuesIfMaxAgeIsExceeded() { - // keys present, but too stale comp. to maxAge - GetOnlineFeaturesRequest request = - GetOnlineFeaturesRequest.newBuilder() - .addFeatures( - FeatureReference.newBuilder() - .setName("feature1") - .setVersion(1) - .setProject("project") - .build()) - .addFeatures( - FeatureReference.newBuilder() - .setName("feature2") - .setVersion(1) - .setProject("project") - .build()) - .addEntityRows( - EntityRow.newBuilder() - .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) - .putFields("entity1", intValue(1)) - .putFields("entity2", strValue("a"))) - .addEntityRows( - EntityRow.newBuilder() - .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) - .putFields("entity1", intValue(2)) - .putFields("entity2", strValue("b"))) - .build(); - - List featureRows = - Lists.newArrayList( - FeatureRow.newBuilder() - .setEventTimestamp(Timestamp.newBuilder().setSeconds(100)) - .addAllFields( - Lists.newArrayList( - Field.newBuilder().setName("entity1").setValue(intValue(1)).build(), - Field.newBuilder().setName("entity2").setValue(strValue("a")).build(), - Field.newBuilder().setName("feature1").setValue(intValue(1)).build(), - Field.newBuilder().setName("feature2").setValue(intValue(1)).build())) - .setFeatureSet("featureSet:1") - .build(), - FeatureRow.newBuilder() - .setEventTimestamp( - Timestamp.newBuilder().setSeconds(50)) // this value should be nulled - .addAllFields( - Lists.newArrayList( - Field.newBuilder().setName("entity1").setValue(intValue(2)).build(), - Field.newBuilder().setName("entity2").setValue(strValue("b")).build(), - Field.newBuilder().setName("feature1").setValue(intValue(2)).build(), - Field.newBuilder().setName("feature2").setValue(intValue(2)).build())) - .setFeatureSet("featureSet:1") - .build()); - - FeatureSetSpec spec = - getFeatureSetSpec().toBuilder().setMaxAge(Duration.newBuilder().setSeconds(1)).build(); - FeatureSetRequest featureSetRequest = - FeatureSetRequest.newBuilder() - .addAllFeatureReferences(request.getFeaturesList()) - .setSpec(spec) - .build(); - - List featureRowBytes = - featureRows.stream().map(AbstractMessageLite::toByteArray).collect(Collectors.toList()); - when(specService.getFeatureSets(request.getFeaturesList())) - .thenReturn(Collections.singletonList(featureSetRequest)); - when(jedisPool.getResource()).thenReturn(jedis); - when(jedis.mget(redisKeyList)).thenReturn(featureRowBytes); - when(tracer.buildSpan(ArgumentMatchers.any())).thenReturn(Mockito.mock(SpanBuilder.class)); - - GetOnlineFeaturesResponse expected = - GetOnlineFeaturesResponse.newBuilder() - .addFieldValues( - FieldValues.newBuilder() - .putFields("entity1", intValue(1)) - .putFields("entity2", strValue("a")) - .putFields("project/feature1:1", intValue(1)) - .putFields("project/feature2:1", intValue(1))) - .addFieldValues( - FieldValues.newBuilder() - .putFields("entity1", intValue(2)) - .putFields("entity2", strValue("b")) - .putFields("project/feature1:1", Value.newBuilder().build()) - .putFields("project/feature2:1", Value.newBuilder().build())) - .build(); - GetOnlineFeaturesResponse actual = redisServingService.getOnlineFeatures(request); - assertThat( - responseToMapList(actual), containsInAnyOrder(responseToMapList(expected).toArray())); - } - - @Test - public void shouldFilterOutUndesiredRows() { - // requested rows less than the rows available in the featureset - GetOnlineFeaturesRequest request = - GetOnlineFeaturesRequest.newBuilder() - .addFeatures( - FeatureReference.newBuilder() - .setName("feature1") - .setVersion(1) - .setProject("project") - .build()) - .addEntityRows( - EntityRow.newBuilder() - .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) - .putFields("entity1", intValue(1)) - .putFields("entity2", strValue("a"))) - .addEntityRows( - EntityRow.newBuilder() - .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) - .putFields("entity1", intValue(2)) - .putFields("entity2", strValue("b"))) - .build(); - - List featureRows = - Lists.newArrayList( - FeatureRow.newBuilder() - .setEventTimestamp(Timestamp.newBuilder().setSeconds(100)) - .addAllFields( - Lists.newArrayList( - Field.newBuilder().setName("entity1").setValue(intValue(1)).build(), - Field.newBuilder().setName("entity2").setValue(strValue("a")).build(), - Field.newBuilder().setName("feature1").setValue(intValue(1)).build(), - Field.newBuilder().setName("feature2").setValue(intValue(1)).build())) - .setFeatureSet("featureSet:1") - .build(), - FeatureRow.newBuilder() - .setEventTimestamp(Timestamp.newBuilder().setSeconds(100)) - .addAllFields( - Lists.newArrayList( - Field.newBuilder().setName("entity1").setValue(intValue(2)).build(), - Field.newBuilder().setName("entity2").setValue(strValue("b")).build(), - Field.newBuilder().setName("feature1").setValue(intValue(2)).build(), - Field.newBuilder().setName("feature2").setValue(intValue(2)).build())) - .setFeatureSet("featureSet:1") - .build()); - - FeatureSetRequest featureSetRequest = - FeatureSetRequest.newBuilder() - .addAllFeatureReferences(request.getFeaturesList()) - .setSpec(getFeatureSetSpec()) - .build(); - - List featureRowBytes = - featureRows.stream().map(AbstractMessageLite::toByteArray).collect(Collectors.toList()); - when(specService.getFeatureSets(request.getFeaturesList())) - .thenReturn(Collections.singletonList(featureSetRequest)); - when(jedisPool.getResource()).thenReturn(jedis); - when(jedis.mget(redisKeyList)).thenReturn(featureRowBytes); - when(tracer.buildSpan(ArgumentMatchers.any())).thenReturn(Mockito.mock(SpanBuilder.class)); - - GetOnlineFeaturesResponse expected = - GetOnlineFeaturesResponse.newBuilder() - .addFieldValues( - FieldValues.newBuilder() - .putFields("entity1", intValue(1)) - .putFields("entity2", strValue("a")) - .putFields("project/feature1:1", intValue(1))) - .addFieldValues( - FieldValues.newBuilder() - .putFields("entity1", intValue(2)) - .putFields("entity2", strValue("b")) - .putFields("project/feature1:1", intValue(2))) - .build(); - GetOnlineFeaturesResponse actual = redisServingService.getOnlineFeatures(request); - assertThat( - responseToMapList(actual), containsInAnyOrder(responseToMapList(expected).toArray())); - } - - private List> responseToMapList(GetOnlineFeaturesResponse response) { - return response.getFieldValuesList().stream() - .map(FieldValues::getFieldsMap) - .collect(Collectors.toList()); - } - - private Value intValue(int val) { - return Value.newBuilder().setInt64Val(val).build(); - } - - private Value strValue(String val) { - return Value.newBuilder().setStringVal(val).build(); - } - - private FeatureSetSpec getFeatureSetSpec() { - return FeatureSetSpec.newBuilder() - .setProject("project") - .setName("featureSet") - .setVersion(1) - .addEntities(EntitySpec.newBuilder().setName("entity1")) - .addEntities(EntitySpec.newBuilder().setName("entity2")) - .setMaxAge(Duration.newBuilder().setSeconds(30)) // default - .build(); - } - - private FeatureSetSpec getFeatureSetSpecWithNoMaxAge() { - return FeatureSetSpec.newBuilder() - .setProject("project") - .setName("featureSet") - .setVersion(1) - .addEntities(EntitySpec.newBuilder().setName("entity1")) - .addEntities(EntitySpec.newBuilder().setName("entity2")) - .setMaxAge(Duration.newBuilder().setSeconds(0).setNanos(0).build()) - .build(); - } -} diff --git a/serving/src/test/java/feast/serving/util/mappers/YamlToProtoMapperTest.java b/serving/src/test/java/feast/serving/util/mappers/YamlToProtoMapperTest.java deleted file mode 100644 index 6f95f5307b2..00000000000 --- a/serving/src/test/java/feast/serving/util/mappers/YamlToProtoMapperTest.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright 2018-2019 The Feast Authors - * - * 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 feast.serving.util.mappers; - -import static org.hamcrest.core.IsEqual.equalTo; -import static org.junit.Assert.*; - -import feast.core.StoreProto.Store; -import feast.core.StoreProto.Store.RedisConfig; -import feast.core.StoreProto.Store.StoreType; -import feast.core.StoreProto.Store.Subscription; -import java.io.IOException; -import org.junit.Test; - -public class YamlToProtoMapperTest { - - @Test - public void shouldConvertYamlToProto() throws IOException { - String yaml = - "name: test\n" - + "type: REDIS\n" - + "redis_config:\n" - + " host: localhost\n" - + " port: 6379\n" - + "subscriptions:\n" - + "- project: \"*\"\n" - + " name: \"*\"\n" - + " version: \"*\"\n"; - Store store = YamlToProtoMapper.yamlToStoreProto(yaml); - Store expected = - Store.newBuilder() - .setName("test") - .setType(StoreType.REDIS) - .setRedisConfig(RedisConfig.newBuilder().setHost("localhost").setPort(6379)) - .addSubscriptions( - Subscription.newBuilder().setProject("*").setName("*").setVersion("*")) - .build(); - assertThat(store, equalTo(expected)); - } -} diff --git a/storage/api/pom.xml b/storage/api/pom.xml new file mode 100644 index 00000000000..c1648c7cfa1 --- /dev/null +++ b/storage/api/pom.xml @@ -0,0 +1,72 @@ + + + + dev.feast + feast-parent + ${revision} + ../.. + + + 4.0.0 + feast-storage-api + + Feast Storage API + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + + javax.annotation + + + + + + + + + + dev.feast + datatypes-java + ${project.version} + + + + org.apache.beam + beam-sdks-java-core + ${org.apache.beam.version} + + + + com.google.auto.value + auto-value-annotations + 1.6.6 + + + + com.google.auto.value + auto-value + 1.6.6 + provided + + + + org.apache.commons + commons-lang3 + 3.9 + + + + junit + junit + 4.12 + test + + + + diff --git a/serving/src/main/java/feast/serving/specs/FeatureSetRequest.java b/storage/api/src/main/java/feast/storage/api/retriever/FeatureSetRequest.java similarity index 79% rename from serving/src/main/java/feast/serving/specs/FeatureSetRequest.java rename to storage/api/src/main/java/feast/storage/api/retriever/FeatureSetRequest.java index 904630659d7..3482529a97d 100644 --- a/serving/src/main/java/feast/serving/specs/FeatureSetRequest.java +++ b/storage/api/src/main/java/feast/storage/api/retriever/FeatureSetRequest.java @@ -14,13 +14,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package feast.serving.specs; +package feast.storage.api.retriever; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableSet; -import feast.core.FeatureSetProto.FeatureSetSpec; -import feast.serving.ServingAPIProto.FeatureReference; +import feast.proto.core.FeatureSetProto.FeatureSetSpec; +import feast.proto.serving.ServingAPIProto.FeatureReference; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; @AutoValue public abstract class FeatureSetRequest { @@ -50,4 +52,9 @@ public Builder addFeatureReference(FeatureReference featureReference) { public abstract FeatureSetRequest build(); } + + public Map getFeatureRefsByName() { + return getFeatureReferences().stream() + .collect(Collectors.toMap(FeatureReference::getName, featureReference -> featureReference)); + } } diff --git a/storage/api/src/main/java/feast/storage/api/retriever/HistoricalRetrievalResult.java b/storage/api/src/main/java/feast/storage/api/retriever/HistoricalRetrievalResult.java new file mode 100644 index 00000000000..6127ce616f2 --- /dev/null +++ b/storage/api/src/main/java/feast/storage/api/retriever/HistoricalRetrievalResult.java @@ -0,0 +1,100 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.storage.api.retriever; + +import com.google.auto.value.AutoValue; +import feast.proto.serving.ServingAPIProto.DataFormat; +import feast.proto.serving.ServingAPIProto.JobStatus; +import java.io.Serializable; +import java.util.List; +import javax.annotation.Nullable; + +/** Result of a historical feature retrieval request. */ +@AutoValue +public abstract class HistoricalRetrievalResult implements Serializable { + + public abstract String getId(); + + public abstract JobStatus getStatus(); + + @Nullable + public abstract String getError(); + + @Nullable + public abstract List getFileUris(); + + @Nullable + public abstract DataFormat getDataFormat(); + + /** + * Instantiates a {@link HistoricalRetrievalResult} indicating that the retrieval was a failure, + * together with its associated error. + * + * @param id retrieval id identifying the retrieval request. + * @param error error that occurred + * @return {@link HistoricalRetrievalResult} + */ + public static HistoricalRetrievalResult error(String id, Exception error) { + return newBuilder() + .setId(id) + .setStatus(JobStatus.JOB_STATUS_DONE) + .setError(error.getMessage()) + .build(); + } + + /** + * Instantiates a {@link HistoricalRetrievalResult} indicating that the retrieval was a success, + * together with the location of the output. + * + * @param id retrieval id identifying the retrieval request + * @param fileUris list of output file URIs + * @param dataFormat data format of the output files + * @return + */ + public static HistoricalRetrievalResult success( + String id, List fileUris, DataFormat dataFormat) { + return newBuilder() + .setId(id) + .setStatus(JobStatus.JOB_STATUS_DONE) + .setFileUris(fileUris) + .setDataFormat(dataFormat) + .build(); + } + + static Builder newBuilder() { + return new AutoValue_HistoricalRetrievalResult.Builder(); + } + + @AutoValue.Builder + abstract static class Builder { + abstract Builder setId(String id); + + abstract Builder setStatus(JobStatus jobStatus); + + abstract Builder setError(String error); + + abstract Builder setFileUris(List fileUris); + + abstract Builder setDataFormat(DataFormat dataFormat); + + abstract HistoricalRetrievalResult build(); + } + + public boolean hasError() { + return getError() != null; + } +} diff --git a/storage/api/src/main/java/feast/storage/api/retriever/HistoricalRetriever.java b/storage/api/src/main/java/feast/storage/api/retriever/HistoricalRetriever.java new file mode 100644 index 00000000000..f07a6cd0dfd --- /dev/null +++ b/storage/api/src/main/java/feast/storage/api/retriever/HistoricalRetriever.java @@ -0,0 +1,49 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.storage.api.retriever; + +import feast.proto.serving.ServingAPIProto.DatasetSource; +import java.util.List; + +/** + * A historical retriever is a feature retriever that retrieves feature data corresponding to + * provided entities over a given period of time. + */ +public interface HistoricalRetriever { + + /** + * Get temporary staging location if applicable. If not applicable to this store, returns an empty + * string. + * + * @return staging location uri + */ + String getStagingLocation(); + + /** + * Get all features corresponding to the provided batch features request. + * + * @param retrievalId String that uniquely identifies this retrieval request. + * @param datasetSource {@link DatasetSource} containing source to load the dataset containing + * entity columns. + * @param featureSetRequests List of {@link FeatureSetRequest} to feature references in the + * request tied to that feature set. + * @return {@link HistoricalRetrievalResult} if successful, contains the location of the results, + * else contains the error to be returned to the user. + */ + HistoricalRetrievalResult getHistoricalFeatures( + String retrievalId, DatasetSource datasetSource, List featureSetRequests); +} diff --git a/storage/api/src/main/java/feast/storage/api/retriever/OnlineRetriever.java b/storage/api/src/main/java/feast/storage/api/retriever/OnlineRetriever.java new file mode 100644 index 00000000000..45d6b352632 --- /dev/null +++ b/storage/api/src/main/java/feast/storage/api/retriever/OnlineRetriever.java @@ -0,0 +1,40 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.storage.api.retriever; + +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequest.EntityRow; +import feast.proto.types.FeatureRowProto.FeatureRow; +import java.util.List; + +/** + * An online retriever is a feature retriever that retrieves the latest feature data corresponding + * to provided entities. + */ +public interface OnlineRetriever { + + /** + * Get all values corresponding to the request. + * + * @param entityRows list of entity rows in the feature request + * @param featureSetRequests List of {@link FeatureSetRequest} to feature references in the + * request tied to that feature set. + * @return list of lists of {@link FeatureRow}s corresponding to each feature set request and + * entity row. + */ + List> getOnlineFeatures( + List entityRows, List featureSetRequests); +} diff --git a/storage/api/src/main/java/feast/storage/api/writer/DeadletterSink.java b/storage/api/src/main/java/feast/storage/api/writer/DeadletterSink.java new file mode 100644 index 00000000000..a07254bddb0 --- /dev/null +++ b/storage/api/src/main/java/feast/storage/api/writer/DeadletterSink.java @@ -0,0 +1,38 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.storage.api.writer; + +import org.apache.beam.sdk.transforms.PTransform; +import org.apache.beam.sdk.values.PCollection; +import org.apache.beam.sdk.values.PDone; + +/** Interface for for implementing user defined deadletter sinks to write failed elements to. */ +public interface DeadletterSink { + + /** + * Set up the deadletter sink for writes. This method will be called once during pipeline + * initialisation. + */ + void prepareWrite(); + + /** + * Get a {@link PTransform} that writes a collection of FailedElements to the deadletter sink. + * + * @return {@link PTransform} + */ + PTransform, PDone> write(); +} diff --git a/ingestion/src/main/java/feast/ingestion/values/FailedElement.java b/storage/api/src/main/java/feast/storage/api/writer/FailedElement.java similarity index 93% rename from ingestion/src/main/java/feast/ingestion/values/FailedElement.java rename to storage/api/src/main/java/feast/storage/api/writer/FailedElement.java index 9606c27d190..c6db877216f 100644 --- a/ingestion/src/main/java/feast/ingestion/values/FailedElement.java +++ b/storage/api/src/main/java/feast/storage/api/writer/FailedElement.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package feast.ingestion.values; +package feast.storage.api.writer; import com.google.auto.value.AutoValue; import javax.annotation.Nullable; @@ -39,9 +39,6 @@ public abstract class FailedElement { @Nullable public abstract String getFeatureSetName(); - @Nullable - public abstract String getFeatureSetVersion(); - @Nullable public abstract String getTransformName(); @@ -66,8 +63,6 @@ public abstract static class Builder { public abstract Builder setFeatureSetName(String featureSetName); - public abstract Builder setFeatureSetVersion(String featureSetVersion); - public abstract Builder setJobName(String jobName); public abstract Builder setTransformName(String transformName); diff --git a/storage/api/src/main/java/feast/storage/api/writer/FeatureSink.java b/storage/api/src/main/java/feast/storage/api/writer/FeatureSink.java new file mode 100644 index 00000000000..f49ea9f423e --- /dev/null +++ b/storage/api/src/main/java/feast/storage/api/writer/FeatureSink.java @@ -0,0 +1,54 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.storage.api.writer; + +import feast.proto.core.FeatureSetProto; +import feast.proto.types.FeatureRowProto.FeatureRow; +import java.io.Serializable; +import org.apache.beam.sdk.transforms.PTransform; +import org.apache.beam.sdk.values.PCollection; + +/** Interface for implementing user defined feature sink functionality. */ +public interface FeatureSink extends Serializable { + + /** + * Set up storage backend for write. This method will be called once during pipeline + * initialisation. + * + *

Examples when schemas need to be updated: + * + *

    + *
  • when a new entity is registered, a table usually needs to be created + *
  • when a new feature is registered, a column with appropriate data type usually needs to be + * created + *
+ * + *

If the storage backend is a key-value or a schema-less database, however, there may not be a + * need to manage any schemas. + * + * @param featureSet Feature set to be written + */ + void prepareWrite(FeatureSetProto.FeatureSet featureSet); + + /** + * Get a {@link PTransform} that writes feature rows to the store, and returns a {@link + * WriteResult} that splits successful and failed inserts to be separately logged. + * + * @return {@link PTransform} + */ + PTransform, WriteResult> writer(); +} diff --git a/storage/api/src/main/java/feast/storage/api/writer/WriteResult.java b/storage/api/src/main/java/feast/storage/api/writer/WriteResult.java new file mode 100644 index 00000000000..abe06e41ee8 --- /dev/null +++ b/storage/api/src/main/java/feast/storage/api/writer/WriteResult.java @@ -0,0 +1,97 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.storage.api.writer; + +import com.google.common.collect.ImmutableMap; +import feast.proto.types.FeatureRowProto.FeatureRow; +import java.io.Serializable; +import java.util.Map; +import org.apache.beam.sdk.Pipeline; +import org.apache.beam.sdk.transforms.PTransform; +import org.apache.beam.sdk.values.*; + +/** The result of a write transform. */ +public final class WriteResult implements Serializable, POutput { + + private final Pipeline pipeline; + private final PCollection successfulInserts; + private final PCollection failedInserts; + + private static TupleTag successfulInsertsTag = new TupleTag<>("successfulInserts"); + private static TupleTag failedInsertsTupleTag = new TupleTag<>("failedInserts"); + + /** + * Creates a {@link WriteResult} in the given {@link Pipeline}. + * + * @param pipeline {@link Pipeline} + * @param successfulInserts {@link PCollection} of {@link FeatureRow}s successfully inserted into + * the store + * @param failedInserts {@link PCollection} of {@link FailedElement}s + * @return {@link WriteResult} + */ + public static WriteResult in( + Pipeline pipeline, + PCollection successfulInserts, + PCollection failedInserts) { + return new WriteResult(pipeline, successfulInserts, failedInserts); + } + + private WriteResult( + Pipeline pipeline, + PCollection successfulInserts, + PCollection failedInserts) { + + this.pipeline = pipeline; + this.successfulInserts = successfulInserts; + this.failedInserts = failedInserts; + } + + /** + * Gets set of feature rows that were unsuccessfully written to the store. The failed feature rows + * are wrapped in FailedElement objects so implementations of WriteResult can be flexible in how + * errors are stored. + * + * @return FailedElements of unsuccessfully written feature rows + */ + public PCollection getFailedInserts() { + return failedInserts; + } + + /** + * Gets set of successfully written feature rows. + * + * @return PCollection of feature rows successfully written to the store + */ + public PCollection getSuccessfulInserts() { + return successfulInserts; + } + + @Override + public Pipeline getPipeline() { + return pipeline; + } + + @Override + public Map, PValue> expand() { + return ImmutableMap.of( + successfulInsertsTag, successfulInserts, failedInsertsTupleTag, failedInserts); + } + + @Override + public void finishSpecifyingOutput( + String transformName, PInput input, PTransform transform) {} +} diff --git a/storage/api/src/main/java/feast/storage/common/retry/BackOffExecutor.java b/storage/api/src/main/java/feast/storage/common/retry/BackOffExecutor.java new file mode 100644 index 00000000000..296582f8b35 --- /dev/null +++ b/storage/api/src/main/java/feast/storage/common/retry/BackOffExecutor.java @@ -0,0 +1,58 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.storage.common.retry; + +import java.io.Serializable; +import org.apache.beam.sdk.util.BackOff; +import org.apache.beam.sdk.util.BackOffUtils; +import org.apache.beam.sdk.util.FluentBackoff; +import org.apache.beam.sdk.util.Sleeper; +import org.joda.time.Duration; + +public class BackOffExecutor implements Serializable { + + private final Integer maxRetries; + private final Duration initialBackOff; + + public BackOffExecutor(Integer maxRetries, Duration initialBackOff) { + this.maxRetries = maxRetries; + this.initialBackOff = initialBackOff; + } + + public void execute(Retriable retriable) throws Exception { + FluentBackoff backoff = + FluentBackoff.DEFAULT.withMaxRetries(maxRetries).withInitialBackoff(initialBackOff); + execute(retriable, backoff); + } + + private void execute(Retriable retriable, FluentBackoff backoff) throws Exception { + Sleeper sleeper = Sleeper.DEFAULT; + BackOff backOff = backoff.backoff(); + while (true) { + try { + retriable.execute(); + break; + } catch (Exception e) { + if (retriable.isExceptionRetriable(e) && BackOffUtils.next(sleeper, backOff)) { + retriable.cleanUpAfterFailure(); + } else { + throw e; + } + } + } + } +} diff --git a/core/src/main/java/feast/core/job/dataflow/DataflowJobConfig.java b/storage/api/src/main/java/feast/storage/common/retry/Retriable.java similarity index 73% rename from core/src/main/java/feast/core/job/dataflow/DataflowJobConfig.java rename to storage/api/src/main/java/feast/storage/common/retry/Retriable.java index a9bbf345d19..2c92c851758 100644 --- a/core/src/main/java/feast/core/job/dataflow/DataflowJobConfig.java +++ b/storage/api/src/main/java/feast/storage/common/retry/Retriable.java @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: Apache-2.0 - * Copyright 2018-2019 The Feast Authors + * Copyright 2018-2020 The Feast Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,12 +14,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package feast.core.job.dataflow; +package feast.storage.common.retry; -import lombok.Value; +public interface Retriable { + void execute() throws Exception; -@Value -public class DataflowJobConfig { - private String projectId; - private String location; + Boolean isExceptionRetriable(Exception e); + + void cleanUpAfterFailure(); } diff --git a/storage/api/src/main/java/feast/storage/common/testing/TestUtil.java b/storage/api/src/main/java/feast/storage/common/testing/TestUtil.java new file mode 100644 index 00000000000..43a96e97efa --- /dev/null +++ b/storage/api/src/main/java/feast/storage/common/testing/TestUtil.java @@ -0,0 +1,188 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2019 The Feast Authors + * + * 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 feast.storage.common.testing; + +import com.google.protobuf.ByteString; +import com.google.protobuf.util.Timestamps; +import feast.proto.core.FeatureSetProto.FeatureSet; +import feast.proto.core.FeatureSetProto.FeatureSetSpec; +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.proto.types.FeatureRowProto.FeatureRow.Builder; +import feast.proto.types.FieldProto.Field; +import feast.proto.types.ValueProto.*; +import java.util.concurrent.ThreadLocalRandom; +import org.apache.commons.lang3.RandomStringUtils; + +@SuppressWarnings("WeakerAccess") +public class TestUtil { + + /** + * Create a Feature Row with random value according to the FeatureSetSpec + * + * @param featureSet {@link FeatureSet} + * @return {@link FeatureRow} + */ + public static FeatureRow createRandomFeatureRow(FeatureSet featureSet) { + ThreadLocalRandom random = ThreadLocalRandom.current(); + int randomStringSizeMaxSize = 12; + return createRandomFeatureRow(featureSet, random.nextInt(0, randomStringSizeMaxSize) + 4); + } + + /** + * Create a Feature Row with random value according to the FeatureSet. + * + *

The Feature Row created contains fields according to the entities and features defined in + * FeatureSet, matching the value type of the field, with randomized value for testing. + * + * @param featureSet {@link FeatureSet} + * @param randomStringSize number of characters for the generated random string + * @return {@link FeatureRow} + */ + public static FeatureRow createRandomFeatureRow(FeatureSet featureSet, int randomStringSize) { + Builder builder = + FeatureRow.newBuilder() + .setFeatureSet(getFeatureSetReference(featureSet)) + .setEventTimestamp(Timestamps.fromMillis(System.currentTimeMillis())); + + featureSet + .getSpec() + .getEntitiesList() + .forEach( + field -> { + builder.addFields( + Field.newBuilder() + .setName(field.getName()) + .setValue(createRandomValue(field.getValueType(), randomStringSize)) + .build()); + }); + + featureSet + .getSpec() + .getFeaturesList() + .forEach( + field -> { + builder.addFields( + Field.newBuilder() + .setName(field.getName()) + .setValue(createRandomValue(field.getValueType(), randomStringSize)) + .build()); + }); + + return builder.build(); + } + + private static String getFeatureSetReference(FeatureSet featureSet) { + FeatureSetSpec spec = featureSet.getSpec(); + return String.format("%s/%s:%d", spec.getProject(), spec.getName()); + } + + /** + * Create a random Feast {@link Value} of {@link ValueType.Enum}. + * + * @param type {@link ValueType.Enum} + * @param randomStringSize number of characters for the generated random string + * @return {@link Value} + */ + public static Value createRandomValue(ValueType.Enum type, int randomStringSize) { + Value.Builder builder = Value.newBuilder(); + ThreadLocalRandom random = ThreadLocalRandom.current(); + + switch (type) { + case INVALID: + case UNRECOGNIZED: + throw new IllegalArgumentException("Invalid ValueType: " + type); + case BYTES: + builder.setBytesVal( + ByteString.copyFrom(RandomStringUtils.randomAlphanumeric(randomStringSize).getBytes())); + break; + case STRING: + builder.setStringVal(RandomStringUtils.randomAlphanumeric(randomStringSize)); + break; + case INT32: + builder.setInt32Val(random.nextInt()); + break; + case INT64: + builder.setInt64Val(random.nextLong()); + break; + case DOUBLE: + builder.setDoubleVal(random.nextDouble()); + break; + case FLOAT: + builder.setFloatVal(random.nextFloat()); + break; + case BOOL: + builder.setBoolVal(random.nextBoolean()); + break; + case BYTES_LIST: + builder.setBytesListVal( + BytesList.newBuilder() + .addVal( + ByteString.copyFrom( + RandomStringUtils.randomAlphanumeric(randomStringSize).getBytes())) + .build()); + break; + case STRING_LIST: + builder.setStringListVal( + StringList.newBuilder() + .addVal(RandomStringUtils.randomAlphanumeric(randomStringSize)) + .build()); + break; + case INT32_LIST: + builder.setInt32ListVal(Int32List.newBuilder().addVal(random.nextInt()).build()); + break; + case INT64_LIST: + builder.setInt64ListVal(Int64List.newBuilder().addVal(random.nextLong()).build()); + break; + case DOUBLE_LIST: + builder.setDoubleListVal(DoubleList.newBuilder().addVal(random.nextDouble()).build()); + break; + case FLOAT_LIST: + builder.setFloatListVal(FloatList.newBuilder().addVal(random.nextFloat()).build()); + break; + case BOOL_LIST: + builder.setBoolListVal(BoolList.newBuilder().addVal(random.nextBoolean()).build()); + break; + } + return builder.build(); + } + + /** + * Create a field object with given name and type. + * + * @param name of the field. + * @param value of the field. Should be compatible with the valuetype given. + * @param valueType type of the field. + * @return Field object + */ + public static Field field(String name, Object value, ValueType.Enum valueType) { + Field.Builder fieldBuilder = Field.newBuilder().setName(name); + switch (valueType) { + case INT32: + return fieldBuilder.setValue(Value.newBuilder().setInt32Val((int) value)).build(); + case INT64: + return fieldBuilder.setValue(Value.newBuilder().setInt64Val((int) value)).build(); + case FLOAT: + return fieldBuilder.setValue(Value.newBuilder().setFloatVal((float) value)).build(); + case DOUBLE: + return fieldBuilder.setValue(Value.newBuilder().setDoubleVal((double) value)).build(); + case STRING: + return fieldBuilder.setValue(Value.newBuilder().setStringVal((String) value)).build(); + default: + throw new IllegalStateException("Unexpected valueType: " + value.getClass()); + } + } +} diff --git a/storage/connectors/bigquery/pom.xml b/storage/connectors/bigquery/pom.xml new file mode 100644 index 00000000000..fab3739c43c --- /dev/null +++ b/storage/connectors/bigquery/pom.xml @@ -0,0 +1,94 @@ + + + + dev.feast + feast-storage-connectors + ${revision} + + + 4.0.0 + feast-storage-connector-bigquery + + Feast Storage Connector for BigQuery + + + + io.pebbletemplates + pebble + 3.1.0 + + + + + com.google.cloud + google-cloud-bigquery + + + + com.google.cloud + google-cloud-storage + + + + org.apache.beam + beam-sdks-java-io-google-cloud-platform + ${org.apache.beam.version} + + + com.google.cloud + google-cloud-spanner + + + com.google.cloud.bigtable + bigtable-client-core + + + + + + io.opencensus + opencensus-contrib-http-util + 0.21.0 + + + + com.google.auto.value + auto-value-annotations + 1.6.6 + + + + com.google.auto.value + auto-value + 1.6.6 + provided + + + + junit + junit + 4.12 + test + + + + org.apache.beam + beam-runners-direct-java + ${org.apache.beam.version} + test + + + + org.hamcrest + hamcrest-core + test + + + org.hamcrest + hamcrest-library + test + + + + diff --git a/storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/common/TypeUtil.java b/storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/common/TypeUtil.java new file mode 100644 index 00000000000..a8e4cda678d --- /dev/null +++ b/storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/common/TypeUtil.java @@ -0,0 +1,66 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.storage.connectors.bigquery.common; + +import com.google.cloud.bigquery.StandardSQLTypeName; +import feast.proto.types.ValueProto; +import java.util.HashMap; +import java.util.Map; + +public class TypeUtil { + + private static final Map + VALUE_TYPE_TO_STANDARD_SQL_TYPE = new HashMap<>(); + + static { + VALUE_TYPE_TO_STANDARD_SQL_TYPE.put(ValueProto.ValueType.Enum.BYTES, StandardSQLTypeName.BYTES); + VALUE_TYPE_TO_STANDARD_SQL_TYPE.put( + ValueProto.ValueType.Enum.STRING, StandardSQLTypeName.STRING); + VALUE_TYPE_TO_STANDARD_SQL_TYPE.put(ValueProto.ValueType.Enum.INT32, StandardSQLTypeName.INT64); + VALUE_TYPE_TO_STANDARD_SQL_TYPE.put(ValueProto.ValueType.Enum.INT64, StandardSQLTypeName.INT64); + VALUE_TYPE_TO_STANDARD_SQL_TYPE.put( + ValueProto.ValueType.Enum.DOUBLE, StandardSQLTypeName.FLOAT64); + VALUE_TYPE_TO_STANDARD_SQL_TYPE.put( + ValueProto.ValueType.Enum.FLOAT, StandardSQLTypeName.FLOAT64); + VALUE_TYPE_TO_STANDARD_SQL_TYPE.put(ValueProto.ValueType.Enum.BOOL, StandardSQLTypeName.BOOL); + VALUE_TYPE_TO_STANDARD_SQL_TYPE.put( + ValueProto.ValueType.Enum.BYTES_LIST, StandardSQLTypeName.BYTES); + VALUE_TYPE_TO_STANDARD_SQL_TYPE.put( + ValueProto.ValueType.Enum.STRING_LIST, StandardSQLTypeName.STRING); + VALUE_TYPE_TO_STANDARD_SQL_TYPE.put( + ValueProto.ValueType.Enum.INT32_LIST, StandardSQLTypeName.INT64); + VALUE_TYPE_TO_STANDARD_SQL_TYPE.put( + ValueProto.ValueType.Enum.INT64_LIST, StandardSQLTypeName.INT64); + VALUE_TYPE_TO_STANDARD_SQL_TYPE.put( + ValueProto.ValueType.Enum.DOUBLE_LIST, StandardSQLTypeName.FLOAT64); + VALUE_TYPE_TO_STANDARD_SQL_TYPE.put( + ValueProto.ValueType.Enum.FLOAT_LIST, StandardSQLTypeName.FLOAT64); + VALUE_TYPE_TO_STANDARD_SQL_TYPE.put( + ValueProto.ValueType.Enum.BOOL_LIST, StandardSQLTypeName.BOOL); + } + + /** + * Converts {@link feast.proto.types.ValueProto.ValueType} to its corresponding {@link + * StandardSQLTypeName} + * + * @param valueType value type to convert + * @return {@link StandardSQLTypeName} + */ + public static StandardSQLTypeName toStandardSqlType(ValueProto.ValueType.Enum valueType) { + return VALUE_TYPE_TO_STANDARD_SQL_TYPE.get(valueType); + } +} diff --git a/storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/retriever/BigQueryHistoricalRetriever.java b/storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/retriever/BigQueryHistoricalRetriever.java new file mode 100644 index 00000000000..4b2ee45543e --- /dev/null +++ b/storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/retriever/BigQueryHistoricalRetriever.java @@ -0,0 +1,438 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.storage.connectors.bigquery.retriever; + +import static feast.storage.connectors.bigquery.retriever.QueryTemplater.createEntityTableUUIDQuery; +import static feast.storage.connectors.bigquery.retriever.QueryTemplater.createTimestampLimitQuery; + +import com.google.auto.value.AutoValue; +import com.google.cloud.RetryOption; +import com.google.cloud.bigquery.*; +import com.google.cloud.storage.Blob; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; +import feast.proto.serving.ServingAPIProto; +import feast.proto.serving.ServingAPIProto.DatasetSource; +import feast.storage.api.retriever.FeatureSetRequest; +import feast.storage.api.retriever.HistoricalRetrievalResult; +import feast.storage.api.retriever.HistoricalRetriever; +import io.grpc.Status; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.*; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.threeten.bp.Duration; + +@AutoValue +public abstract class BigQueryHistoricalRetriever implements HistoricalRetriever { + + private static final Logger log = + org.slf4j.LoggerFactory.getLogger(BigQueryHistoricalRetriever.class); + + public static final long TEMP_TABLE_EXPIRY_DURATION_MS = Duration.ofDays(1).toMillis(); + private static final long SUBQUERY_TIMEOUT_SECS = 900; // 15 minutes + + public static HistoricalRetriever create(Map config) { + + BigQuery bigquery = BigQueryOptions.getDefaultInstance().getService(); + Storage storage = StorageOptions.getDefaultInstance().getService(); + + String jobStagingLocation = config.get("staging_location"); + if (!jobStagingLocation.contains("://")) { + throw new IllegalArgumentException( + String.format("jobStagingLocation is not a valid URI: %s", jobStagingLocation)); + } + if (jobStagingLocation.endsWith("/")) { + jobStagingLocation = jobStagingLocation.substring(0, jobStagingLocation.length() - 1); + } + if (!jobStagingLocation.startsWith("gs://")) { + throw new IllegalArgumentException( + "Store type BIGQUERY requires job staging location to be a valid and existing Google Cloud Storage URI. Invalid staging location: " + + jobStagingLocation); + } + + return builder() + .setBigquery(bigquery) + .setDatasetId(config.get("dataset_id")) + .setProjectId(config.get("project_id")) + .setJobStagingLocation(config.get("staging_location")) + .setInitialRetryDelaySecs(Integer.parseInt(config.get("initial_retry_delay_seconds"))) + .setTotalTimeoutSecs(Integer.parseInt(config.get("total_timeout_seconds"))) + .setStorage(storage) + .build(); + } + + public abstract String projectId(); + + public abstract String datasetId(); + + public abstract BigQuery bigquery(); + + public abstract String jobStagingLocation(); + + public abstract int initialRetryDelaySecs(); + + public abstract int totalTimeoutSecs(); + + public abstract Storage storage(); + + public static Builder builder() { + return new AutoValue_BigQueryHistoricalRetriever.Builder(); + } + + @AutoValue.Builder + public abstract static class Builder { + public abstract Builder setProjectId(String projectId); + + public abstract Builder setDatasetId(String datasetId); + + public abstract Builder setJobStagingLocation(String jobStagingLocation); + + public abstract Builder setBigquery(BigQuery bigquery); + + public abstract Builder setInitialRetryDelaySecs(int initialRetryDelaySecs); + + public abstract Builder setTotalTimeoutSecs(int totalTimeoutSecs); + + public abstract Builder setStorage(Storage storage); + + public abstract BigQueryHistoricalRetriever build(); + } + + @Override + public String getStagingLocation() { + return jobStagingLocation(); + } + + @Override + public HistoricalRetrievalResult getHistoricalFeatures( + String retrievalId, DatasetSource datasetSource, List featureSetRequests) { + List featureSetQueryInfos = + QueryTemplater.getFeatureSetInfos(featureSetRequests); + + // 1. load entity table + Table entityTable; + String entityTableName; + try { + entityTable = loadEntities(datasetSource); + + TableId entityTableWithUUIDs = generateUUIDs(entityTable); + entityTableName = generateFullTableName(entityTableWithUUIDs); + } catch (Exception e) { + return HistoricalRetrievalResult.error( + retrievalId, + new RuntimeException( + String.format("Unable to load entity table to BigQuery: %s", e.toString()))); + } + + Schema entityTableSchema = entityTable.getDefinition().getSchema(); + List entityTableColumnNames = + entityTableSchema.getFields().stream() + .map(Field::getName) + .filter(name -> !name.equals("event_timestamp")) + .collect(Collectors.toList()); + + // 2. Retrieve the temporal bounds of the entity dataset provided + FieldValueList timestampLimits = getTimestampLimits(entityTableName); + + // 3. Generate the subqueries + List featureSetQueries = + generateQueries(entityTableName, timestampLimits, featureSetQueryInfos); + + QueryJobConfiguration queryConfig; + + try { + // 4. Run the subqueries in parallel then collect the outputs + Job queryJob = + runBatchQuery( + entityTableName, entityTableColumnNames, featureSetQueryInfos, featureSetQueries); + queryConfig = queryJob.getConfiguration(); + String exportTableDestinationUri = + String.format("%s/%s/*.avro", jobStagingLocation(), retrievalId); + + // 5. Export the table + // Hardcode the format to Avro for now + ExtractJobConfiguration extractConfig = + ExtractJobConfiguration.of( + queryConfig.getDestinationTable(), exportTableDestinationUri, "Avro"); + Job extractJob = bigquery().create(JobInfo.of(extractConfig)); + waitForJob(extractJob); + + } catch (BigQueryException | InterruptedException | IOException e) { + return HistoricalRetrievalResult.error(retrievalId, e); + } + + List fileUris = parseOutputFileURIs(retrievalId); + + return HistoricalRetrievalResult.success( + retrievalId, fileUris, ServingAPIProto.DataFormat.DATA_FORMAT_AVRO); + } + + private TableId generateUUIDs(Table loadedEntityTable) { + try { + String uuidQuery = + createEntityTableUUIDQuery(generateFullTableName(loadedEntityTable.getTableId())); + QueryJobConfiguration queryJobConfig = + QueryJobConfiguration.newBuilder(uuidQuery) + .setDestinationTable(TableId.of(projectId(), datasetId(), createTempTableName())) + .build(); + Job queryJob = bigquery().create(JobInfo.of(queryJobConfig)); + Job completedJob = waitForJob(queryJob); + TableInfo expiry = + bigquery() + .getTable(queryJobConfig.getDestinationTable()) + .toBuilder() + .setExpirationTime(System.currentTimeMillis() + TEMP_TABLE_EXPIRY_DURATION_MS) + .build(); + bigquery().update(expiry); + queryJobConfig = completedJob.getConfiguration(); + return queryJobConfig.getDestinationTable(); + } catch (InterruptedException | BigQueryException e) { + throw Status.INTERNAL + .withDescription("Failed to load entity dataset into store") + .withCause(e) + .asRuntimeException(); + } + } + + private FieldValueList getTimestampLimits(String entityTableName) { + QueryJobConfiguration getTimestampLimitsQuery = + QueryJobConfiguration.newBuilder(createTimestampLimitQuery(entityTableName)) + .setDefaultDataset(DatasetId.of(projectId(), datasetId())) + .setDestinationTable(TableId.of(projectId(), datasetId(), createTempTableName())) + .build(); + try { + Job job = bigquery().create(JobInfo.of(getTimestampLimitsQuery)); + TableResult getTimestampLimitsQueryResult = waitForJob(job).getQueryResults(); + TableInfo expiry = + bigquery() + .getTable(getTimestampLimitsQuery.getDestinationTable()) + .toBuilder() + .setExpirationTime(System.currentTimeMillis() + TEMP_TABLE_EXPIRY_DURATION_MS) + .build(); + bigquery().update(expiry); + FieldValueList result = null; + for (FieldValueList fields : getTimestampLimitsQueryResult.getValues()) { + result = fields; + } + if (result == null || result.get("min").isNull() || result.get("max").isNull()) { + throw new RuntimeException("query returned insufficient values"); + } + return result; + } catch (InterruptedException e) { + throw Status.INTERNAL + .withDescription("Unable to extract min and max timestamps from query") + .withCause(e) + .asRuntimeException(); + } + } + + private Table loadEntities(ServingAPIProto.DatasetSource datasetSource) { + Table loadedEntityTable; + switch (datasetSource.getDatasetSourceCase()) { + case FILE_SOURCE: + try { + // Currently only AVRO format is supported + if (datasetSource.getFileSource().getDataFormat() + != ServingAPIProto.DataFormat.DATA_FORMAT_AVRO) { + throw Status.INVALID_ARGUMENT + .withDescription("Invalid file format, only AVRO is supported.") + .asRuntimeException(); + } + + TableId tableId = TableId.of(projectId(), datasetId(), createTempTableName()); + log.info( + "Loading entity rows to: {}.{}.{}", projectId(), datasetId(), tableId.getTable()); + + LoadJobConfiguration loadJobConfiguration = + LoadJobConfiguration.of( + tableId, datasetSource.getFileSource().getFileUrisList(), FormatOptions.avro()); + loadJobConfiguration = + loadJobConfiguration.toBuilder().setUseAvroLogicalTypes(true).build(); + Job job = bigquery().create(JobInfo.of(loadJobConfiguration)); + waitForJob(job); + + TableInfo expiry = + bigquery() + .getTable(tableId) + .toBuilder() + .setExpirationTime(System.currentTimeMillis() + TEMP_TABLE_EXPIRY_DURATION_MS) + .build(); + bigquery().update(expiry); + + loadedEntityTable = bigquery().getTable(tableId); + if (!loadedEntityTable.exists()) { + throw new RuntimeException( + "Unable to create entity dataset table, table already exists"); + } + return loadedEntityTable; + } catch (Exception e) { + log.error("Exception has occurred in loadEntities method: ", e); + throw Status.INTERNAL + .withDescription("Failed to load entity dataset into store: " + e.toString()) + .withCause(e) + .asRuntimeException(); + } + case DATASETSOURCE_NOT_SET: + default: + throw Status.INVALID_ARGUMENT + .withDescription("Data source must be set.") + .asRuntimeException(); + } + } + + private List generateQueries( + String entityTableName, + FieldValueList timestampLimits, + List featureSetQueryInfos) { + List featureSetQueries = new ArrayList<>(); + try { + for (FeatureSetQueryInfo featureSetInfo : featureSetQueryInfos) { + String query = + QueryTemplater.createFeatureSetPointInTimeQuery( + featureSetInfo, + projectId(), + datasetId(), + entityTableName, + timestampLimits.get("min").getStringValue(), + timestampLimits.get("max").getStringValue()); + featureSetQueries.add(query); + } + } catch (IOException e) { + throw Status.INTERNAL + .withDescription("Unable to generate query for batch retrieval") + .withCause(e) + .asRuntimeException(); + } + return featureSetQueries; + } + + Job runBatchQuery( + String entityTableName, + List entityTableColumnNames, + List featureSetQueryInfos, + List featureSetQueries) + throws BigQueryException, InterruptedException, IOException { + ExecutorService executorService = Executors.newFixedThreadPool(featureSetQueries.size()); + ExecutorCompletionService executorCompletionService = + new ExecutorCompletionService<>(executorService); + + // For each of the feature sets requested, start an async job joining the features in that + // feature set to the provided entity table + for (int i = 0; i < featureSetQueries.size(); i++) { + QueryJobConfiguration queryJobConfig = + QueryJobConfiguration.newBuilder(featureSetQueries.get(i)) + .setDestinationTable(TableId.of(projectId(), datasetId(), createTempTableName())) + .build(); + Job subqueryJob = bigquery().create(JobInfo.of(queryJobConfig)); + executorCompletionService.submit( + SubqueryCallable.builder() + .setBigquery(bigquery()) + .setFeatureSetInfo(featureSetQueryInfos.get(i)) + .setSubqueryJob(subqueryJob) + .build()); + } + + List completedFeatureSetQueryInfos = new ArrayList<>(); + + for (int i = 0; i < featureSetQueries.size(); i++) { + try { + // Try to retrieve the outputs of all the jobs. The timeout here is a formality; + // a stricter timeout is implemented in the actual SubqueryCallable. + FeatureSetQueryInfo featureSetInfo = + executorCompletionService.take().get(SUBQUERY_TIMEOUT_SECS, TimeUnit.SECONDS); + completedFeatureSetQueryInfos.add(featureSetInfo); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + executorService.shutdownNow(); + throw Status.INTERNAL + .withDescription("Error running batch query") + .withCause(e) + .asRuntimeException(); + } + } + + // Generate and run a join query to collect the outputs of all the + // subqueries into a single table. + String joinQuery = + QueryTemplater.createJoinQuery( + completedFeatureSetQueryInfos, entityTableColumnNames, entityTableName); + QueryJobConfiguration queryJobConfig = + QueryJobConfiguration.newBuilder(joinQuery) + .setDestinationTable(TableId.of(projectId(), datasetId(), createTempTableName())) + .build(); + Job queryJob = bigquery().create(JobInfo.of(queryJobConfig)); + Job completedQueryJob = waitForJob(queryJob); + + TableInfo expiry = + bigquery() + .getTable(queryJobConfig.getDestinationTable()) + .toBuilder() + .setExpirationTime(System.currentTimeMillis() + TEMP_TABLE_EXPIRY_DURATION_MS) + .build(); + bigquery().update(expiry); + + return completedQueryJob; + } + + private List parseOutputFileURIs(String feastJobId) { + String scheme = jobStagingLocation().substring(0, jobStagingLocation().indexOf("://")); + String stagingLocationNoScheme = + jobStagingLocation().substring(jobStagingLocation().indexOf("://") + 3); + String bucket = stagingLocationNoScheme.split("/")[0]; + List prefixParts = new ArrayList<>(); + prefixParts.add( + stagingLocationNoScheme.contains("/") && !stagingLocationNoScheme.endsWith("/") + ? stagingLocationNoScheme.substring(stagingLocationNoScheme.indexOf("/") + 1) + : ""); + prefixParts.add(feastJobId); + String prefix = String.join("/", prefixParts) + "/"; + + List fileUris = new ArrayList<>(); + for (Blob blob : storage().list(bucket, Storage.BlobListOption.prefix(prefix)).iterateAll()) { + fileUris.add(String.format("%s://%s/%s", scheme, blob.getBucket(), blob.getName())); + } + return fileUris; + } + + private Job waitForJob(Job queryJob) throws InterruptedException { + Job completedJob = + queryJob.waitFor( + RetryOption.initialRetryDelay(Duration.ofSeconds(initialRetryDelaySecs())), + RetryOption.totalTimeout(Duration.ofSeconds(totalTimeoutSecs()))); + if (completedJob == null) { + throw Status.INTERNAL.withDescription("Job no longer exists").asRuntimeException(); + } else if (completedJob.getStatus().getError() != null) { + throw Status.INTERNAL + .withDescription("Job failed: " + completedJob.getStatus().getError()) + .asRuntimeException(); + } + return completedJob; + } + + public String generateFullTableName(TableId tableId) { + return String.format( + "%s.%s.%s", tableId.getProject(), tableId.getDataset(), tableId.getTable()); + } + + public String createTempTableName() { + return "_" + UUID.randomUUID().toString().replace("-", ""); + } +} diff --git a/serving/src/main/java/feast/serving/store/bigquery/model/FeatureSetInfo.java b/storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/retriever/FeatureSetQueryInfo.java similarity index 78% rename from serving/src/main/java/feast/serving/store/bigquery/model/FeatureSetInfo.java rename to storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/retriever/FeatureSetQueryInfo.java index 77c80ead0ea..befdc564904 100644 --- a/serving/src/main/java/feast/serving/store/bigquery/model/FeatureSetInfo.java +++ b/storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/retriever/FeatureSetQueryInfo.java @@ -14,42 +14,39 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package feast.serving.store.bigquery.model; +package feast.storage.connectors.bigquery.retriever; +import feast.proto.serving.ServingAPIProto.FeatureReference; import java.util.List; -public class FeatureSetInfo { +public class FeatureSetQueryInfo { private final String project; private final String name; - private final int version; private final long maxAge; private final List entities; - private final List features; + private final List features; private final String table; - public FeatureSetInfo( + public FeatureSetQueryInfo( String project, String name, - int version, long maxAge, List entities, - List features, + List features, String table) { this.project = project; this.name = name; - this.version = version; this.maxAge = maxAge; this.entities = entities; this.features = features; this.table = table; } - public FeatureSetInfo(FeatureSetInfo featureSetInfo, String table) { + public FeatureSetQueryInfo(FeatureSetQueryInfo featureSetInfo, String table) { this.project = featureSetInfo.getProject(); this.name = featureSetInfo.getName(); - this.version = featureSetInfo.getVersion(); this.maxAge = featureSetInfo.getMaxAge(); this.entities = featureSetInfo.getEntities(); this.features = featureSetInfo.getFeatures(); @@ -64,10 +61,6 @@ public String getName() { return name; } - public int getVersion() { - return version; - } - public long getMaxAge() { return maxAge; } @@ -76,7 +69,7 @@ public List getEntities() { return entities; } - public List getFeatures() { + public List getFeatures() { return features; } diff --git a/serving/src/main/java/feast/serving/store/bigquery/QueryTemplater.java b/storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/retriever/QueryTemplater.java similarity index 82% rename from serving/src/main/java/feast/serving/store/bigquery/QueryTemplater.java rename to storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/retriever/QueryTemplater.java index e3f1138db89..969efb36c38 100644 --- a/serving/src/main/java/feast/serving/store/bigquery/QueryTemplater.java +++ b/storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/retriever/QueryTemplater.java @@ -14,17 +14,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package feast.serving.store.bigquery; +package feast.storage.connectors.bigquery.retriever; import com.google.cloud.bigquery.TableId; import com.google.protobuf.Duration; import com.mitchellbosecke.pebble.PebbleEngine; import com.mitchellbosecke.pebble.template.PebbleTemplate; -import feast.core.FeatureSetProto.EntitySpec; -import feast.core.FeatureSetProto.FeatureSetSpec; -import feast.serving.ServingAPIProto.FeatureReference; -import feast.serving.specs.FeatureSetRequest; -import feast.serving.store.bigquery.model.FeatureSetInfo; +import feast.proto.core.FeatureSetProto.EntitySpec; +import feast.proto.core.FeatureSetProto.FeatureSetSpec; +import feast.proto.serving.ServingAPIProto.FeatureReference; +import feast.storage.api.retriever.FeatureSetRequest; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; @@ -67,31 +66,23 @@ public static String createEntityTableUUIDQuery(String leftTableName) { * Generate the information necessary for the sql templating for point in time correctness join to * the entity dataset for each feature set requested. * - * @param featureSetRequests List of feature sets requested + * @param featureSetRequests List of {@link FeatureSetRequest} containing a {@link FeatureSetSpec} + * and its corresponding {@link FeatureReference}s provided by the user. * @return List of FeatureSetInfos */ - public static List getFeatureSetInfos(List featureSetRequests) - throws IllegalArgumentException { + public static List getFeatureSetInfos( + List featureSetRequests) throws IllegalArgumentException { - List featureSetInfos = new ArrayList<>(); + List featureSetInfos = new ArrayList<>(); for (FeatureSetRequest featureSetRequest : featureSetRequests) { FeatureSetSpec spec = featureSetRequest.getSpec(); Duration maxAge = spec.getMaxAge(); List fsEntities = spec.getEntitiesList().stream().map(EntitySpec::getName).collect(Collectors.toList()); - List features = - featureSetRequest.getFeatureReferences().stream() - .map(FeatureReference::getName) - .collect(Collectors.toList()); + List features = featureSetRequest.getFeatureReferences().asList(); featureSetInfos.add( - new FeatureSetInfo( - spec.getProject(), - spec.getName(), - spec.getVersion(), - maxAge.getSeconds(), - fsEntities, - features, - "")); + new FeatureSetQueryInfo( + spec.getProject(), spec.getName(), maxAge.getSeconds(), fsEntities, features, "")); } return featureSetInfos; } @@ -109,7 +100,7 @@ public static List getFeatureSetInfos(List fe * @return point in time correctness join BQ SQL query */ public static String createFeatureSetPointInTimeQuery( - FeatureSetInfo featureSetInfo, + FeatureSetQueryInfo featureSetInfo, String projectId, String datasetId, String leftTableName, @@ -139,7 +130,7 @@ public static String createFeatureSetPointInTimeQuery( * @return query to join temporary feature set tables to the entity table */ public static String createJoinQuery( - List featureSetInfos, + List featureSetInfos, List entityTableColumnNames, String leftTableName) throws IOException { diff --git a/serving/src/main/java/feast/serving/store/bigquery/SubqueryCallable.java b/storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/retriever/SubqueryCallable.java similarity index 63% rename from serving/src/main/java/feast/serving/store/bigquery/SubqueryCallable.java rename to storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/retriever/SubqueryCallable.java index e0b8f457986..43a32cef504 100644 --- a/serving/src/main/java/feast/serving/store/bigquery/SubqueryCallable.java +++ b/storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/retriever/SubqueryCallable.java @@ -14,31 +14,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package feast.serving.store.bigquery; +package feast.storage.connectors.bigquery.retriever; -import static feast.serving.service.BigQueryServingService.TEMP_TABLE_EXPIRY_DURATION_MS; -import static feast.serving.store.bigquery.QueryTemplater.generateFullTableName; +import static feast.storage.connectors.bigquery.retriever.BigQueryHistoricalRetriever.TEMP_TABLE_EXPIRY_DURATION_MS; +import static feast.storage.connectors.bigquery.retriever.QueryTemplater.generateFullTableName; import com.google.auto.value.AutoValue; -import com.google.cloud.bigquery.BigQuery; -import com.google.cloud.bigquery.BigQueryException; -import com.google.cloud.bigquery.Job; -import com.google.cloud.bigquery.QueryJobConfiguration; -import com.google.cloud.bigquery.TableId; -import com.google.cloud.bigquery.TableInfo; -import feast.serving.store.bigquery.model.FeatureSetInfo; +import com.google.cloud.bigquery.*; import java.util.concurrent.Callable; /** - * Waits for a bigquery job to complete; when complete, it updates the feature set info with the - * output table name, as well as increments the completed jobs counter in the query job listener. + * Waits for a point-in-time correctness join to complete. On completion, returns a featureSetInfo + * updated with the reference to the table containing the results of the query. */ @AutoValue -public abstract class SubqueryCallable implements Callable { +public abstract class SubqueryCallable implements Callable { public abstract BigQuery bigquery(); - public abstract FeatureSetInfo featureSetInfo(); + public abstract FeatureSetQueryInfo featureSetInfo(); public abstract Job subqueryJob(); @@ -51,7 +45,7 @@ public abstract static class Builder { public abstract Builder setBigquery(BigQuery bigquery); - public abstract Builder setFeatureSetInfo(FeatureSetInfo featureSetInfo); + public abstract Builder setFeatureSetInfo(FeatureSetQueryInfo featureSetInfo); public abstract Builder setSubqueryJob(Job subqueryJob); @@ -59,7 +53,7 @@ public abstract static class Builder { } @Override - public FeatureSetInfo call() throws BigQueryException, InterruptedException { + public FeatureSetQueryInfo call() throws BigQueryException, InterruptedException { QueryJobConfiguration subqueryConfig; subqueryJob().waitFor(); subqueryConfig = subqueryJob().getConfiguration(); @@ -75,6 +69,6 @@ public FeatureSetInfo call() throws BigQueryException, InterruptedException { String fullTablePath = generateFullTableName(destinationTable); - return new FeatureSetInfo(featureSetInfo(), fullTablePath); + return new FeatureSetQueryInfo(featureSetInfo(), fullTablePath); } } diff --git a/storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/writer/BigQueryDeadletterSink.java b/storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/writer/BigQueryDeadletterSink.java new file mode 100644 index 00000000000..96364c96c78 --- /dev/null +++ b/storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/writer/BigQueryDeadletterSink.java @@ -0,0 +1,133 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.storage.connectors.bigquery.writer; + +import com.google.api.services.bigquery.model.TableRow; +import com.google.api.services.bigquery.model.TimePartitioning; +import com.google.auto.value.AutoValue; +import com.google.common.io.Resources; +import feast.storage.api.writer.DeadletterSink; +import feast.storage.api.writer.FailedElement; +import java.nio.charset.StandardCharsets; +import org.apache.beam.sdk.io.gcp.bigquery.BigQueryIO; +import org.apache.beam.sdk.io.gcp.bigquery.BigQueryIO.Write.CreateDisposition; +import org.apache.beam.sdk.io.gcp.bigquery.BigQueryIO.Write.WriteDisposition; +import org.apache.beam.sdk.transforms.DoFn; +import org.apache.beam.sdk.transforms.PTransform; +import org.apache.beam.sdk.transforms.ParDo; +import org.apache.beam.sdk.values.PCollection; +import org.apache.beam.sdk.values.PDone; +import org.slf4j.Logger; + +public class BigQueryDeadletterSink implements DeadletterSink { + + private static final String DEADLETTER_SCHEMA_FILE_PATH = "schemas/deadletter_table_schema.json"; + private static final Logger log = org.slf4j.LoggerFactory.getLogger(BigQueryDeadletterSink.class); + private static final String TIMESTAMP_COLUMN = "timestamp"; + + private final String tableSpec; + private String jsonSchema; + + public BigQueryDeadletterSink(String tableSpec) { + + this.tableSpec = tableSpec; + try { + jsonSchema = + Resources.toString( + Resources.getResource(DEADLETTER_SCHEMA_FILE_PATH), StandardCharsets.UTF_8); + } catch (Exception e) { + log.error( + "Unable to read {} file from the resources folder!", DEADLETTER_SCHEMA_FILE_PATH, e); + } + } + + @Override + public void prepareWrite() {} + + @Override + public PTransform, PDone> write() { + return WriteFailedElement.newBuilder() + .setJsonSchema(jsonSchema) + .setTableSpec(tableSpec) + .build(); + } + + @AutoValue + public abstract static class WriteFailedElement + extends PTransform, PDone> { + + public abstract String getTableSpec(); + + public abstract String getJsonSchema(); + + public static Builder newBuilder() { + return new AutoValue_BigQueryDeadletterSink_WriteFailedElement.Builder(); + } + + @AutoValue.Builder + public abstract static class Builder { + + /** + * @param tableSpec Table spec should follow the format "PROJECT_ID:DATASET_ID.TABLE_ID". + * Table will be created if not exists. + */ + public abstract Builder setTableSpec(String tableSpec); + + /** + * @param jsonSchema JSON string describing the schema + * of the table. + */ + public abstract Builder setJsonSchema(String jsonSchema); + + public abstract WriteFailedElement build(); + } + + @Override + public PDone expand(PCollection input) { + TimePartitioning partition = new TimePartitioning().setType("DAY"); + partition.setField(TIMESTAMP_COLUMN); + input + .apply("FailedElementToTableRow", ParDo.of(new FailedElementToTableRowFn())) + .apply( + "WriteFailedElementsToBigQuery", + BigQueryIO.writeTableRows() + .to(getTableSpec()) + .withJsonSchema(getJsonSchema()) + .withTimePartitioning(partition) + .withCreateDisposition(CreateDisposition.CREATE_IF_NEEDED) + .withWriteDisposition(WriteDisposition.WRITE_APPEND)); + return PDone.in(input.getPipeline()); + } + } + + public static class FailedElementToTableRowFn extends DoFn { + @ProcessElement + public void processElement(ProcessContext context) { + final FailedElement element = context.element(); + final TableRow tableRow = + new TableRow() + .set(TIMESTAMP_COLUMN, element.getTimestamp().toString()) + .set("job_name", element.getJobName()) + .set("transform_name", element.getTransformName()) + .set("payload", element.getPayload()) + .set("error_message", element.getErrorMessage()) + .set("stack_trace", element.getStackTrace()); + context.output(tableRow); + } + } +} diff --git a/storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/writer/BigQueryFeatureSink.java b/storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/writer/BigQueryFeatureSink.java new file mode 100644 index 00000000000..8dffe629769 --- /dev/null +++ b/storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/writer/BigQueryFeatureSink.java @@ -0,0 +1,214 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.storage.connectors.bigquery.writer; + +import com.google.auto.value.AutoValue; +import com.google.cloud.bigquery.*; +import com.google.common.collect.ImmutableMap; +import feast.proto.core.FeatureSetProto; +import feast.proto.core.StoreProto.Store.BigQueryConfig; +import feast.proto.types.FeatureRowProto; +import feast.storage.api.writer.FeatureSink; +import feast.storage.api.writer.WriteResult; +import feast.storage.connectors.bigquery.common.TypeUtil; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import org.apache.beam.repackaged.core.org.apache.commons.lang3.tuple.Pair; +import org.apache.beam.sdk.transforms.PTransform; +import org.apache.beam.sdk.values.PCollection; +import org.slf4j.Logger; + +@AutoValue +public abstract class BigQueryFeatureSink implements FeatureSink { + private static final Logger log = org.slf4j.LoggerFactory.getLogger(BigQueryFeatureSink.class); + + // Column description for reserved fields + public static final String BIGQUERY_EVENT_TIMESTAMP_FIELD_DESCRIPTION = + "Event time for the FeatureRow"; + public static final String BIGQUERY_CREATED_TIMESTAMP_FIELD_DESCRIPTION = + "Processing time of the FeatureRow ingestion in Feast\""; + public static final String BIGQUERY_INGESTION_ID_FIELD_DESCRIPTION = + "Unique id identifying groups of rows that have been ingested together"; + public static final String BIGQUERY_JOB_ID_FIELD_DESCRIPTION = + "Feast import job ID for the FeatureRow"; + + public abstract String getProjectId(); + + public abstract String getDatasetId(); + + public abstract BigQuery getBigQuery(); + + /** + * Initialize a {@link BigQueryFeatureSink.Builder} from a {@link BigQueryConfig}. This method + * initializes a {@link BigQuery} client with default options. Use the builder method to inject + * your own client. + * + * @param config {@link BigQueryConfig} + * @param featureSetSpecs + * @return {@link BigQueryFeatureSink.Builder} + */ + public static FeatureSink fromConfig( + BigQueryConfig config, Map featureSetSpecs) { + return builder() + .setDatasetId(config.getDatasetId()) + .setProjectId(config.getProjectId()) + .setBigQuery(BigQueryOptions.getDefaultInstance().getService()) + .build(); + } + + public static Builder builder() { + return new AutoValue_BigQueryFeatureSink.Builder(); + } + + @AutoValue.Builder + public abstract static class Builder { + + public abstract Builder setProjectId(String projectId); + + public abstract Builder setDatasetId(String datasetId); + + public abstract Builder setBigQuery(BigQuery bigQuery); + + public abstract BigQueryFeatureSink build(); + } + + /** @param featureSet Feature set to be written */ + @Override + public void prepareWrite(FeatureSetProto.FeatureSet featureSet) { + BigQuery bigquery = getBigQuery(); + FeatureSetProto.FeatureSetSpec featureSetSpec = featureSet.getSpec(); + + DatasetId datasetId = DatasetId.of(getProjectId(), getDatasetId()); + if (bigquery.getDataset(datasetId) == null) { + log.info( + "Creating dataset '{}' in project '{}'", datasetId.getDataset(), datasetId.getProject()); + bigquery.create(DatasetInfo.of(datasetId)); + } + String tableName = + String.format("%s_%s", featureSetSpec.getProject(), featureSetSpec.getName()) + .replaceAll("-", "_"); + TableId tableId = TableId.of(datasetId.getProject(), datasetId.getDataset(), tableName); + + Table table = bigquery.getTable(tableId); + TableDefinition tableDefinition = createBigQueryTableDefinition(table, featureSet.getSpec()); + TableInfo tableInfo = TableInfo.of(tableId, tableDefinition); + if (table != null) { + log.info( + "Updating and writing to existing BigQuery table '{}:{}.{}'", + datasetId.getProject(), + datasetId.getDataset(), + tableName); + bigquery.update(tableInfo); + return; + } + + log.info( + "Creating table '{}' in dataset '{}' in project '{}'", + tableId.getTable(), + datasetId.getDataset(), + datasetId.getProject()); + bigquery.create(tableInfo); + } + + @Override + public PTransform, WriteResult> writer() { + return new BigQueryWrite(DatasetId.of(getProjectId(), getDatasetId())); + } + /** + * Creates a BigQuery {@link TableDefinition} based on the provided FeatureSetSpec and the + * existing table, if any. If a table already exists, existing fields will be retained, and new + * fields present in the feature set will be appended to the existing FieldsList. + * + * @param existingTable existing {@link Table} retrieved using bigquery.GetTable(). If the table + * does not exist, will be null. + * @param spec FeatureSet spec that this table is for + * @return {@link TableDefinition} containing all tombstoned and active fields. + */ + private TableDefinition createBigQueryTableDefinition( + Table existingTable, FeatureSetProto.FeatureSetSpec spec) { + List fields = new ArrayList<>(); + log.info("Table will have the following fields:"); + + for (FeatureSetProto.EntitySpec entitySpec : spec.getEntitiesList()) { + Field.Builder builder = + Field.newBuilder( + entitySpec.getName(), TypeUtil.toStandardSqlType(entitySpec.getValueType())); + if (entitySpec.getValueType().name().toLowerCase().endsWith("_list")) { + builder.setMode(Field.Mode.REPEATED); + } + Field field = builder.build(); + log.info("- {}", field.toString()); + fields.add(field); + } + for (FeatureSetProto.FeatureSpec featureSpec : spec.getFeaturesList()) { + Field.Builder builder = + Field.newBuilder( + featureSpec.getName(), TypeUtil.toStandardSqlType(featureSpec.getValueType())); + if (featureSpec.getValueType().name().toLowerCase().endsWith("_list")) { + builder.setMode(Field.Mode.REPEATED); + } + Field field = builder.build(); + log.info("- {}", field.toString()); + fields.add(field); + } + + // Refer to protos/feast/core/Store.proto for reserved fields in BigQuery. + Map> + reservedFieldNameToPairOfStandardSQLTypeAndDescription = + ImmutableMap.of( + "event_timestamp", + Pair.of(StandardSQLTypeName.TIMESTAMP, BIGQUERY_EVENT_TIMESTAMP_FIELD_DESCRIPTION), + "created_timestamp", + Pair.of( + StandardSQLTypeName.TIMESTAMP, BIGQUERY_CREATED_TIMESTAMP_FIELD_DESCRIPTION), + "ingestion_id", + Pair.of(StandardSQLTypeName.STRING, BIGQUERY_INGESTION_ID_FIELD_DESCRIPTION), + "job_id", + Pair.of(StandardSQLTypeName.STRING, BIGQUERY_JOB_ID_FIELD_DESCRIPTION)); + for (Map.Entry> entry : + reservedFieldNameToPairOfStandardSQLTypeAndDescription.entrySet()) { + Field field = + Field.newBuilder(entry.getKey(), entry.getValue().getLeft()) + .setDescription(entry.getValue().getRight()) + .build(); + log.info("- {}", field.toString()); + fields.add(field); + } + + TimePartitioning timePartitioning = + TimePartitioning.newBuilder(TimePartitioning.Type.DAY).setField("event_timestamp").build(); + log.info("Table partitioning: " + timePartitioning.toString()); + + List fieldsList = new ArrayList<>(); + if (existingTable != null) { + Schema existingSchema = existingTable.getDefinition().getSchema(); + fieldsList.addAll(existingSchema.getFields()); + } + + for (Field field : fields) { + if (!fieldsList.contains(field)) { + fieldsList.add(field); + } + } + + return StandardTableDefinition.newBuilder() + .setTimePartitioning(timePartitioning) + .setSchema(Schema.of(FieldList.of(fieldsList))) + .build(); + } +} diff --git a/storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/writer/BigQueryWrite.java b/storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/writer/BigQueryWrite.java new file mode 100644 index 00000000000..b1309e59b5e --- /dev/null +++ b/storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/writer/BigQueryWrite.java @@ -0,0 +1,107 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.storage.connectors.bigquery.writer; + +import com.google.api.services.bigquery.model.TableDataInsertAllResponse; +import com.google.api.services.bigquery.model.TableRow; +import com.google.cloud.bigquery.DatasetId; +import feast.proto.types.FeatureRowProto; +import feast.storage.api.writer.FailedElement; +import feast.storage.api.writer.WriteResult; +import java.io.IOException; +import org.apache.beam.sdk.io.gcp.bigquery.BigQueryIO; +import org.apache.beam.sdk.io.gcp.bigquery.BigQueryInsertError; +import org.apache.beam.sdk.io.gcp.bigquery.InsertRetryPolicy; +import org.apache.beam.sdk.transforms.DoFn; +import org.apache.beam.sdk.transforms.PTransform; +import org.apache.beam.sdk.transforms.ParDo; +import org.apache.beam.sdk.values.PCollection; +import org.slf4j.Logger; + +/** + * A {@link PTransform} that writes {@link FeatureRowProto FeatureRows} to the specified BigQuery + * dataset, and returns a {@link WriteResult} containing the unsuccessful writes. Since Bigquery + * does not output successful writes, we cannot emit those, and so no success metrics will be + * captured if this sink is used. + */ +public class BigQueryWrite + extends PTransform, WriteResult> { + private static final Logger log = org.slf4j.LoggerFactory.getLogger(BigQueryWrite.class); + + // Destination dataset + private DatasetId destination; + + public BigQueryWrite(DatasetId destination) { + this.destination = destination; + } + + @Override + public WriteResult expand(PCollection input) { + String jobName = input.getPipeline().getOptions().getJobName(); + org.apache.beam.sdk.io.gcp.bigquery.WriteResult bigqueryWriteResult = + input.apply( + "WriteTableRowToBigQuery", + BigQueryIO.write() + .to(new GetTableDestination(destination.getProject(), destination.getDataset())) + .withFormatFunction(new FeatureRowToTableRow(jobName)) + .withCreateDisposition(BigQueryIO.Write.CreateDisposition.CREATE_NEVER) + .withWriteDisposition(BigQueryIO.Write.WriteDisposition.WRITE_APPEND) + .withExtendedErrorInfo() + .withMethod(BigQueryIO.Write.Method.STREAMING_INSERTS) + .withFailedInsertRetryPolicy(InsertRetryPolicy.retryTransientErrors())); + + PCollection failedElements = + bigqueryWriteResult + .getFailedInsertsWithErr() + .apply( + "WrapBigQueryInsertionError", + ParDo.of( + new DoFn() { + @ProcessElement + public void processElement(ProcessContext context) { + TableDataInsertAllResponse.InsertErrors error = + context.element().getError(); + TableRow row = context.element().getRow(); + try { + context.output( + FailedElement.newBuilder() + .setErrorMessage(error.toPrettyString()) + .setPayload(row.toPrettyString()) + .setJobName(context.getPipelineOptions().getJobName()) + .setTransformName("WriteTableRowToBigQuery") + .build()); + } catch (IOException e) { + log.error(e.getMessage()); + } + } + })); + + // Since BigQueryIO does not support emitting successful writes, we set successfulInserts to + // an empty stream, + // and no metrics will be collected. + PCollection successfulInserts = + input.apply( + "dummy", + ParDo.of( + new DoFn() { + @ProcessElement + public void processElement(ProcessContext context) {} + })); + + return WriteResult.in(input.getPipeline(), successfulInserts, failedElements); + } +} diff --git a/ingestion/src/main/java/feast/store/serving/bigquery/FeatureRowToTableRow.java b/storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/writer/FeatureRowToTableRow.java similarity index 93% rename from ingestion/src/main/java/feast/store/serving/bigquery/FeatureRowToTableRow.java rename to storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/writer/FeatureRowToTableRow.java index b89cf832910..f936c951fe3 100644 --- a/ingestion/src/main/java/feast/store/serving/bigquery/FeatureRowToTableRow.java +++ b/storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/writer/FeatureRowToTableRow.java @@ -14,12 +14,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package feast.store.serving.bigquery; +package feast.storage.connectors.bigquery.writer; import com.google.api.services.bigquery.model.TableRow; import com.google.protobuf.util.Timestamps; -import feast.types.FeatureRowProto.FeatureRow; -import feast.types.FieldProto.Field; +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.proto.types.FieldProto.Field; import java.util.Base64; import java.util.stream.Collectors; import org.apache.beam.sdk.transforms.SerializableFunction; @@ -31,6 +31,7 @@ public class FeatureRowToTableRow implements SerializableFunction { private static final String EVENT_TIMESTAMP_COLUMN = "event_timestamp"; private static final String CREATED_TIMESTAMP_COLUMN = "created_timestamp"; + private static final String INGESTION_ID_COLUMN = "ingestion_id"; private static final String JOB_ID_COLUMN = "job_id"; private final String jobId; @@ -47,6 +48,7 @@ public TableRow apply(FeatureRow featureRow) { TableRow tableRow = new TableRow(); tableRow.set(EVENT_TIMESTAMP_COLUMN, Timestamps.toString(featureRow.getEventTimestamp())); tableRow.set(CREATED_TIMESTAMP_COLUMN, Instant.now().toString()); + tableRow.set(INGESTION_ID_COLUMN, featureRow.getIngestionId()); tableRow.set(JOB_ID_COLUMN, jobId); for (Field field : featureRow.getFieldsList()) { diff --git a/ingestion/src/main/java/feast/store/serving/bigquery/GetTableDestination.java b/storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/writer/GetTableDestination.java similarity index 83% rename from ingestion/src/main/java/feast/store/serving/bigquery/GetTableDestination.java rename to storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/writer/GetTableDestination.java index eb37db94498..b7774ae7ec4 100644 --- a/ingestion/src/main/java/feast/store/serving/bigquery/GetTableDestination.java +++ b/storage/connectors/bigquery/src/main/java/feast/storage/connectors/bigquery/writer/GetTableDestination.java @@ -14,10 +14,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package feast.store.serving.bigquery; +package feast.storage.connectors.bigquery.writer; import com.google.api.services.bigquery.model.TimePartitioning; -import feast.types.FeatureRowProto.FeatureRow; +import feast.proto.types.FeatureRowProto.FeatureRow; import org.apache.beam.sdk.io.gcp.bigquery.TableDestination; import org.apache.beam.sdk.transforms.SerializableFunction; import org.apache.beam.sdk.values.ValueInSingleWindow; @@ -35,8 +35,7 @@ public GetTableDestination(String projectId, String datasetId) { @Override public TableDestination apply(ValueInSingleWindow input) { - String[] split = input.getValue().getFeatureSet().split(":"); - String[] splitName = split[0].split("/"); + String[] splitName = input.getValue().getFeatureSet().split("/"); TimePartitioning timePartitioning = new TimePartitioning() @@ -44,8 +43,7 @@ public TableDestination apply(ValueInSingleWindow input) { .setField(FeatureRowToTableRow.getEventTimestampColumn()); return new TableDestination( - String.format( - "%s:%s.%s_%s_v%s", projectId, datasetId, splitName[0], splitName[1], split[1]), + String.format("%s:%s.%s_%s", projectId, datasetId, splitName[0], splitName[1]), String.format("Feast table for %s", input.getValue().getFeatureSet()), timePartitioning); } diff --git a/storage/connectors/bigquery/src/main/resources/schemas/deadletter_table_schema.json b/storage/connectors/bigquery/src/main/resources/schemas/deadletter_table_schema.json new file mode 100644 index 00000000000..92381189073 --- /dev/null +++ b/storage/connectors/bigquery/src/main/resources/schemas/deadletter_table_schema.json @@ -0,0 +1,34 @@ +{ + "fields": [ + { + "name": "timestamp", + "type": "TIMESTAMP", + "mode": "REQUIRED" + }, + { + "name": "job_name", + "type": "STRING", + "mode": "NULLABLE" + }, + { + "name": "transform_name", + "type": "STRING", + "mode": "NULLABLE" + }, + { + "name": "payload", + "type": "STRING", + "mode": "NULLABLE" + }, + { + "name": "error_message", + "type": "STRING", + "mode": "NULLABLE" + }, + { + "name": "stack_trace", + "type": "STRING", + "mode": "NULLABLE" + } + ] +} \ No newline at end of file diff --git a/storage/connectors/bigquery/src/main/resources/templates/join_featuresets.sql b/storage/connectors/bigquery/src/main/resources/templates/join_featuresets.sql new file mode 100644 index 00000000000..ddddac8d2cf --- /dev/null +++ b/storage/connectors/bigquery/src/main/resources/templates/join_featuresets.sql @@ -0,0 +1,24 @@ +/* + Joins the outputs of multiple point-in-time-correctness joins to a single table. + */ +WITH joined as ( +SELECT * FROM `{{ leftTableName }}` +{% for featureSet in featureSets %} +LEFT JOIN ( + SELECT + uuid, + {% for feature in featureSet.features %} + {{ featureSet.project }}__{{ featureSet.name }}__{{ feature.name }}{% if loop.last %}{% else %}, {% endif %} + {% endfor %} + FROM `{{ featureSet.table }}` +) USING (uuid) +{% endfor %} +) SELECT + event_timestamp, + {{ entities | join(', ') }} + {% for featureSet in featureSets %} + {% for feature in featureSet.features %} + ,{{ featureSet.project }}__{{ featureSet.name }}__{{ feature.name }} as {% if feature.featureSet != "" %}{{ featureSet.name }}__{% endif %}{{ feature.name }} + {% endfor %} + {% endfor %} +FROM joined diff --git a/storage/connectors/bigquery/src/main/resources/templates/single_featureset_pit_join.sql b/storage/connectors/bigquery/src/main/resources/templates/single_featureset_pit_join.sql new file mode 100644 index 00000000000..24bdab2c29c --- /dev/null +++ b/storage/connectors/bigquery/src/main/resources/templates/single_featureset_pit_join.sql @@ -0,0 +1,90 @@ +/* + This query template performs the point-in-time correctness join for a single feature set table + to the provided entity table. + + 1. Concatenate the timestamp and entities from the feature set table with the entity dataset. + Feature values are joined to this table later for improved efficiency. + featureset_timestamp is equal to null in rows from the entity dataset. + */ +WITH union_features AS ( +SELECT + -- uuid is a unique identifier for each row in the entity dataset. Generated by `QueryTemplater.createEntityTableUUIDQuery` + uuid, + -- event_timestamp contains the timestamps to join onto + event_timestamp, + -- the feature_timestamp, i.e. the latest occurrence of the requested feature relative to the entity_dataset timestamp + NULL as {{ featureSet.project }}_{{ featureSet.name }}_feature_timestamp, + -- created timestamp of the feature at the corresponding feature_timestamp + NULL as created_timestamp, + -- select only entities belonging to this feature set + {{ featureSet.entities | join(', ')}}, + -- boolean for filtering the dataset later + true AS is_entity_table +FROM `{{leftTableName}}` +UNION ALL +SELECT + NULL as uuid, + event_timestamp, + event_timestamp as {{ featureSet.project }}_{{ featureSet.name }}_feature_timestamp, + created_timestamp, + {{ featureSet.entities | join(', ')}}, + false AS is_entity_table +FROM `{{projectId}}.{{datasetId}}.{{ featureSet.project }}_{{ featureSet.name }}` WHERE event_timestamp <= '{{maxTimestamp}}' +{% if featureSet.maxAge == 0 %}{% else %}AND event_timestamp >= Timestamp_sub(TIMESTAMP '{{ minTimestamp }}', interval {{ featureSet.maxAge }} second){% endif %} +), +/* + 2. Window the data in the unioned dataset, partitioning by entity and ordering by event_timestamp, as + well as is_entity_table. + Within each window, back-fill the feature_timestamp - as a result of this, the null feature_timestamps + in the rows from the entity table should now contain the latest timestamps relative to the row's + event_timestamp. + + For rows where event_timestamp(provided datetime) - feature_timestamp > max age, set the + feature_timestamp to null. + */ +joined AS ( +SELECT + uuid, + event_timestamp, + {{ featureSet.entities | join(', ')}}, + {% for feature in featureSet.features %} + IF(event_timestamp >= {{ featureSet.project }}_{{ featureSet.name }}_feature_timestamp {% if featureSet.maxAge == 0 %}{% else %}AND Timestamp_sub(event_timestamp, interval {{ featureSet.maxAge }} second) < {{ featureSet.project }}_{{ featureSet.name }}_feature_timestamp{% endif %}, {{ featureSet.project }}__{{ featureSet.name }}__{{ feature.name }}, NULL) as {{ featureSet.project }}__{{ featureSet.name }}__{{ feature.name }}{% if loop.last %}{% else %}, {% endif %} + {% endfor %} +FROM ( +SELECT + uuid, + event_timestamp, + {{ featureSet.entities | join(', ')}}, + FIRST_VALUE(created_timestamp IGNORE NULLS) over w AS created_timestamp, + FIRST_VALUE({{ featureSet.project }}_{{ featureSet.name }}_feature_timestamp IGNORE NULLS) over w AS {{ featureSet.project }}_{{ featureSet.name }}_feature_timestamp, + is_entity_table +FROM union_features +WINDOW w AS (PARTITION BY {{ featureSet.entities | join(', ') }} ORDER BY event_timestamp DESC, is_entity_table DESC, created_timestamp DESC ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) +) +/* + 3. Select only the rows from the entity table, and join the features from the original feature set table + to the dataset using the entity values, feature_timestamp, and created_timestamps. + */ +LEFT JOIN ( +SELECT + event_timestamp as {{ featureSet.project }}_{{ featureSet.name }}_feature_timestamp, + created_timestamp, + {{ featureSet.entities | join(', ')}}, + {% for feature in featureSet.features %} + {{ feature.name }} as {{ featureSet.project }}__{{ featureSet.name }}__{{ feature.name }}{% if loop.last %}{% else %}, {% endif %} + {% endfor %} +FROM `{{ projectId }}.{{ datasetId }}.{{ featureSet.project }}_{{ featureSet.name }}` WHERE event_timestamp <= '{{maxTimestamp}}' +{% if featureSet.maxAge == 0 %}{% else %}AND event_timestamp >= Timestamp_sub(TIMESTAMP '{{ minTimestamp }}', interval {{ featureSet.maxAge }} second){% endif %} +) USING ({{ featureSet.project }}_{{ featureSet.name }}_feature_timestamp, created_timestamp, {{ featureSet.entities | join(', ')}}) +WHERE is_entity_table +) +/* + 4. Finally, deduplicate the rows by selecting the first occurrence of each entity table row UUID. + */ +SELECT + k.* +FROM ( + SELECT ARRAY_AGG(row LIMIT 1)[OFFSET(0)] k + FROM joined row + GROUP BY uuid +) \ No newline at end of file diff --git a/storage/connectors/pom.xml b/storage/connectors/pom.xml new file mode 100644 index 00000000000..280b0d40bf1 --- /dev/null +++ b/storage/connectors/pom.xml @@ -0,0 +1,57 @@ + + + + dev.feast + feast-parent + ${revision} + ../.. + + + 4.0.0 + feast-storage-connectors + pom + + Feast Storage Connectors + + + redis + rediscluster + bigquery + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + + javax.annotation + + + + + + org.jacoco + jacoco-maven-plugin + + + + + + + dev.feast + datatypes-java + ${project.version} + + + + dev.feast + feast-storage-api + ${project.version} + + + + diff --git a/storage/connectors/redis/pom.xml b/storage/connectors/redis/pom.xml new file mode 100644 index 00000000000..6c50895bd20 --- /dev/null +++ b/storage/connectors/redis/pom.xml @@ -0,0 +1,82 @@ + + + + dev.feast + feast-storage-connectors + ${revision} + + + 4.0.0 + feast-storage-connector-redis + + Feast Storage Connector for Redis + + + + io.lettuce + lettuce-core + + + + org.apache.commons + commons-lang3 + 3.9 + + + + com.google.auto.value + auto-value-annotations + 1.6.6 + + + + com.google.auto.value + auto-value + 1.6.6 + provided + + + + org.mockito + mockito-core + 2.23.0 + test + + + + + com.github.kstyrc + embedded-redis + test + + + + org.apache.beam + beam-runners-direct-java + ${org.apache.beam.version} + test + + + + org.hamcrest + hamcrest-core + test + + + + org.hamcrest + hamcrest-library + test + + + + + junit + junit + 4.12 + test + + + + diff --git a/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/retriever/FeatureRowDecoder.java b/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/retriever/FeatureRowDecoder.java new file mode 100644 index 00000000000..fd9556841e4 --- /dev/null +++ b/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/retriever/FeatureRowDecoder.java @@ -0,0 +1,95 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.storage.connectors.redis.retriever; + +import feast.proto.core.FeatureSetProto.FeatureSetSpec; +import feast.proto.core.FeatureSetProto.FeatureSpec; +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.proto.types.FieldProto.Field; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class FeatureRowDecoder { + + private final String featureSetRef; + private final FeatureSetSpec spec; + + public FeatureRowDecoder(String featureSetRef, FeatureSetSpec spec) { + this.featureSetRef = featureSetRef; + this.spec = spec; + } + + /** + * A feature row is considered encoded if the feature set and field names are not set. This method + * is required for backward compatibility purposes, to allow Feast serving to continue serving non + * encoded Feature Row ingested by an older version of Feast. + * + * @param featureRow Feature row + * @return boolean + */ + public Boolean isEncoded(FeatureRow featureRow) { + return featureRow.getFeatureSet().isEmpty() + && featureRow.getFieldsList().stream().allMatch(field -> field.getName().isEmpty()); + } + + /** + * Validates if an encoded feature row can be decoded without exception. + * + * @param featureRow Feature row + * @return boolean + */ + public Boolean isEncodingValid(FeatureRow featureRow) { + return featureRow.getFieldsList().size() == spec.getFeaturesList().size(); + } + + /** + * Decoding feature row by repopulating the field names based on the corresponding feature set + * spec. + * + * @param encodedFeatureRow Feature row + * @return boolean + */ + public FeatureRow decode(FeatureRow encodedFeatureRow) { + final List fieldsWithoutName = encodedFeatureRow.getFieldsList(); + + List featureNames = + spec.getFeaturesList().stream() + .sorted(Comparator.comparing(FeatureSpec::getName)) + .map(FeatureSpec::getName) + .collect(Collectors.toList()); + List fields = + IntStream.range(0, featureNames.size()) + .mapToObj( + featureNameIndex -> { + String featureName = featureNames.get(featureNameIndex); + return fieldsWithoutName + .get(featureNameIndex) + .toBuilder() + .setName(featureName) + .build(); + }) + .collect(Collectors.toList()); + return encodedFeatureRow + .toBuilder() + .clearFields() + .setFeatureSet(featureSetRef) + .addAllFields(fields) + .build(); + } +} diff --git a/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/retriever/RedisOnlineRetriever.java b/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/retriever/RedisOnlineRetriever.java new file mode 100644 index 00000000000..0db4837c069 --- /dev/null +++ b/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/retriever/RedisOnlineRetriever.java @@ -0,0 +1,218 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.storage.connectors.redis.retriever; + +import com.google.protobuf.AbstractMessageLite; +import com.google.protobuf.InvalidProtocolBufferException; +import feast.proto.core.FeatureSetProto.EntitySpec; +import feast.proto.core.FeatureSetProto.FeatureSetSpec; +import feast.proto.serving.ServingAPIProto.FeatureReference; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequest.EntityRow; +import feast.proto.storage.RedisProto.RedisKey; +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.proto.types.FieldProto.Field; +import feast.proto.types.ValueProto.Value; +import feast.storage.api.retriever.FeatureSetRequest; +import feast.storage.api.retriever.OnlineRetriever; +import io.grpc.Status; +import io.lettuce.core.RedisClient; +import io.lettuce.core.RedisURI; +import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.core.api.sync.RedisCommands; +import io.lettuce.core.codec.ByteArrayCodec; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; + +public class RedisOnlineRetriever implements OnlineRetriever { + + private final RedisCommands syncCommands; + + private RedisOnlineRetriever(StatefulRedisConnection connection) { + this.syncCommands = connection.sync(); + } + + public static OnlineRetriever create(Map config) { + + StatefulRedisConnection connection = + RedisClient.create( + RedisURI.create(config.get("host"), Integer.parseInt(config.get("port")))) + .connect(new ByteArrayCodec()); + + return new RedisOnlineRetriever(connection); + } + + public static OnlineRetriever create(StatefulRedisConnection connection) { + return new RedisOnlineRetriever(connection); + } + + /** + * Gets online features from redis. This method returns a list of {@link FeatureRow}s + * corresponding to each feature set spec. Each feature row in the list then corresponds to an + * {@link EntityRow} provided by the user. + * + * @param entityRows list of entity rows in the feature request + * @param featureSetRequests Map of {@link feast.proto.core.FeatureSetProto.FeatureSetSpec} to + * feature references in the request tied to that feature set. + * @return List of List of {@link FeatureRow} + */ + @Override + public List> getOnlineFeatures( + List entityRows, List featureSetRequests) { + + List> featureRows = new ArrayList<>(); + for (FeatureSetRequest featureSetRequest : featureSetRequests) { + List redisKeys = buildRedisKeys(entityRows, featureSetRequest.getSpec()); + try { + List featureRowsForFeatureSet = + sendAndProcessMultiGet( + redisKeys, + featureSetRequest.getSpec(), + featureSetRequest.getFeatureReferences().asList()); + featureRows.add(featureRowsForFeatureSet); + } catch (InvalidProtocolBufferException | ExecutionException e) { + throw Status.INTERNAL + .withDescription("Unable to parse protobuf while retrieving feature") + .withCause(e) + .asRuntimeException(); + } + } + return featureRows; + } + + private List buildRedisKeys(List entityRows, FeatureSetSpec featureSetSpec) { + String featureSetRef = generateFeatureSetStringRef(featureSetSpec); + List featureSetEntityNames = + featureSetSpec.getEntitiesList().stream() + .map(EntitySpec::getName) + .collect(Collectors.toList()); + List redisKeys = + entityRows.stream() + .map(row -> makeRedisKey(featureSetRef, featureSetEntityNames, row)) + .collect(Collectors.toList()); + return redisKeys; + } + + /** + * Create {@link RedisKey} + * + * @param featureSet featureSet reference of the feature. E.g. feature_set_1 + * @param featureSetEntityNames entity names that belong to the featureSet + * @param entityRow entityRow to build the key from + * @return {@link RedisKey} + */ + private RedisKey makeRedisKey( + String featureSet, List featureSetEntityNames, EntityRow entityRow) { + RedisKey.Builder builder = RedisKey.newBuilder().setFeatureSet(featureSet); + Map fieldsMap = entityRow.getFieldsMap(); + featureSetEntityNames.sort(String::compareTo); + for (int i = 0; i < featureSetEntityNames.size(); i++) { + String entityName = featureSetEntityNames.get(i); + + if (!fieldsMap.containsKey(entityName)) { + throw Status.INVALID_ARGUMENT + .withDescription( + String.format( + "Entity row fields \"%s\" does not contain required entity field \"%s\"", + fieldsMap.keySet().toString(), entityName)) + .asRuntimeException(); + } + + builder.addEntities( + Field.newBuilder().setName(entityName).setValue(fieldsMap.get(entityName))); + } + return builder.build(); + } + + private List sendAndProcessMultiGet( + List redisKeys, + FeatureSetSpec featureSetSpec, + List featureReferences) + throws InvalidProtocolBufferException, ExecutionException { + + List values = sendMultiGet(redisKeys); + List featureRows = new ArrayList<>(); + + FeatureRow.Builder nullFeatureRowBuilder = + FeatureRow.newBuilder().setFeatureSet(generateFeatureSetStringRef(featureSetSpec)); + for (FeatureReference featureReference : featureReferences) { + nullFeatureRowBuilder.addFields(Field.newBuilder().setName(featureReference.getName())); + } + + for (int i = 0; i < values.size(); i++) { + + byte[] value = values.get(i); + if (value == null) { + featureRows.add(nullFeatureRowBuilder.build()); + continue; + } + + FeatureRow featureRow = FeatureRow.parseFrom(value); + String featureSetRef = redisKeys.get(i).getFeatureSet(); + FeatureRowDecoder decoder = new FeatureRowDecoder(featureSetRef, featureSetSpec); + if (decoder.isEncoded(featureRow)) { + if (decoder.isEncodingValid(featureRow)) { + featureRow = decoder.decode(featureRow); + } else { + featureRows.add(nullFeatureRowBuilder.build()); + continue; + } + } + + featureRows.add(featureRow); + } + return featureRows; + } + + /** + * Send a list of get request as an mget + * + * @param keys list of {@link RedisKey} + * @return list of {@link FeatureRow} in primitive byte representation for each {@link RedisKey} + */ + private List sendMultiGet(List keys) { + try { + byte[][] binaryKeys = + keys.stream() + .map(AbstractMessageLite::toByteArray) + .collect(Collectors.toList()) + .toArray(new byte[0][0]); + return syncCommands.mget(binaryKeys).stream() + .map( + keyValue -> { + if (keyValue == null) { + return null; + } + return keyValue.getValueOrElse(null); + }) + .collect(Collectors.toList()); + } catch (Exception e) { + throw Status.NOT_FOUND + .withDescription("Unable to retrieve feature from Redis") + .withCause(e) + .asRuntimeException(); + } + } + + // TODO: Refactor this out to common package? + private static String generateFeatureSetStringRef(FeatureSetSpec featureSetSpec) { + String ref = String.format("%s/%s", featureSetSpec.getProject(), featureSetSpec.getName()); + return ref; + } +} diff --git a/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/writer/RedisCustomIO.java b/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/writer/RedisCustomIO.java new file mode 100644 index 00000000000..3fe9148eed5 --- /dev/null +++ b/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/writer/RedisCustomIO.java @@ -0,0 +1,292 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2019 The Feast Authors + * + * 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 feast.storage.connectors.redis.writer; + +import feast.proto.core.FeatureSetProto.EntitySpec; +import feast.proto.core.FeatureSetProto.FeatureSetSpec; +import feast.proto.core.FeatureSetProto.FeatureSpec; +import feast.proto.core.StoreProto.Store.RedisConfig; +import feast.proto.storage.RedisProto.RedisKey; +import feast.proto.storage.RedisProto.RedisKey.Builder; +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.proto.types.FieldProto.Field; +import feast.proto.types.ValueProto; +import feast.storage.api.writer.FailedElement; +import feast.storage.api.writer.WriteResult; +import feast.storage.common.retry.Retriable; +import io.lettuce.core.RedisConnectionException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; +import org.apache.beam.sdk.transforms.DoFn; +import org.apache.beam.sdk.transforms.PTransform; +import org.apache.beam.sdk.transforms.ParDo; +import org.apache.beam.sdk.transforms.windowing.GlobalWindow; +import org.apache.beam.sdk.values.PCollection; +import org.apache.beam.sdk.values.PCollectionTuple; +import org.apache.beam.sdk.values.TupleTag; +import org.apache.beam.sdk.values.TupleTagList; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.joda.time.Instant; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RedisCustomIO { + + private static final int DEFAULT_BATCH_SIZE = 1000; + private static final int DEFAULT_TIMEOUT = 2000; + + private static TupleTag successfulInsertsTag = new TupleTag<>("successfulInserts") {}; + private static TupleTag failedInsertsTupleTag = new TupleTag<>("failedInserts") {}; + + private static final Logger log = LoggerFactory.getLogger(RedisCustomIO.class); + + private RedisCustomIO() {} + + public static Write write(RedisConfig redisConfig, Map featureSetSpecs) { + return new Write(redisConfig, featureSetSpecs); + } + + /** ServingStoreWrite data to a Redis server. */ + public static class Write extends PTransform, WriteResult> { + + private Map featureSetSpecs; + private RedisConfig redisConfig; + private int batchSize; + private int timeout; + + public Write(RedisConfig redisConfig, Map featureSetSpecs) { + + this.redisConfig = redisConfig; + this.featureSetSpecs = featureSetSpecs; + } + + public Write withBatchSize(int batchSize) { + this.batchSize = batchSize; + return this; + } + + public Write withTimeout(int timeout) { + this.timeout = timeout; + return this; + } + + @Override + public WriteResult expand(PCollection input) { + PCollectionTuple redisWrite = + input.apply( + ParDo.of(new WriteDoFn(redisConfig, featureSetSpecs)) + .withOutputTags(successfulInsertsTag, TupleTagList.of(failedInsertsTupleTag))); + return WriteResult.in( + input.getPipeline(), + redisWrite.get(successfulInsertsTag), + redisWrite.get(failedInsertsTupleTag)); + } + + public static class WriteDoFn extends DoFn { + + private final List featureRows = new ArrayList<>(); + private Map featureSetSpecs; + private int batchSize = DEFAULT_BATCH_SIZE; + private int timeout = DEFAULT_TIMEOUT; + private RedisIngestionClient redisIngestionClient; + + WriteDoFn(RedisConfig config, Map featureSetSpecs) { + + this.redisIngestionClient = new RedisStandaloneIngestionClient(config); + this.featureSetSpecs = featureSetSpecs; + } + + public WriteDoFn withBatchSize(int batchSize) { + if (batchSize > 0) { + this.batchSize = batchSize; + } + return this; + } + + public WriteDoFn withTimeout(int timeout) { + if (timeout > 0) { + this.timeout = timeout; + } + return this; + } + + @Setup + public void setup() { + this.redisIngestionClient.setup(); + } + + @StartBundle + public void startBundle() { + try { + redisIngestionClient.connect(); + } catch (RedisConnectionException e) { + log.error("Connection to redis cannot be established ", e); + } + featureRows.clear(); + } + + private void executeBatch() throws Exception { + this.redisIngestionClient + .getBackOffExecutor() + .execute( + new Retriable() { + @Override + public void execute() throws ExecutionException, InterruptedException { + if (!redisIngestionClient.isConnected()) { + redisIngestionClient.connect(); + } + featureRows.forEach( + row -> { + redisIngestionClient.set(getKey(row), getValue(row)); + }); + redisIngestionClient.sync(); + } + + @Override + public Boolean isExceptionRetriable(Exception e) { + return e instanceof RedisConnectionException; + } + + @Override + public void cleanUpAfterFailure() {} + }); + } + + private FailedElement toFailedElement( + FeatureRow featureRow, Exception exception, String jobName) { + return FailedElement.newBuilder() + .setJobName(jobName) + .setTransformName("RedisCustomIO") + .setPayload(featureRow.toString()) + .setErrorMessage(exception.getMessage()) + .setStackTrace(ExceptionUtils.getStackTrace(exception)) + .build(); + } + + private byte[] getKey(FeatureRow featureRow) { + FeatureSetSpec featureSetSpec = featureSetSpecs.get(featureRow.getFeatureSet()); + List entityNames = + featureSetSpec.getEntitiesList().stream() + .map(EntitySpec::getName) + .sorted() + .collect(Collectors.toList()); + + Map entityFields = new HashMap<>(); + Builder redisKeyBuilder = RedisKey.newBuilder().setFeatureSet(featureRow.getFeatureSet()); + for (Field field : featureRow.getFieldsList()) { + if (entityNames.contains(field.getName())) { + entityFields.putIfAbsent( + field.getName(), + Field.newBuilder().setName(field.getName()).setValue(field.getValue()).build()); + } + } + for (String entityName : entityNames) { + redisKeyBuilder.addEntities(entityFields.get(entityName)); + } + return redisKeyBuilder.build().toByteArray(); + } + + private byte[] getValue(FeatureRow featureRow) { + FeatureSetSpec spec = featureSetSpecs.get(featureRow.getFeatureSet()); + + List featureNames = + spec.getFeaturesList().stream().map(FeatureSpec::getName).collect(Collectors.toList()); + Map fieldValueOnlyMap = + featureRow.getFieldsList().stream() + .filter(field -> featureNames.contains(field.getName())) + .distinct() + .collect( + Collectors.toMap( + Field::getName, + field -> Field.newBuilder().setValue(field.getValue()).build())); + + List values = + featureNames.stream() + .sorted() + .map( + featureName -> + fieldValueOnlyMap.getOrDefault( + featureName, + Field.newBuilder() + .setValue(ValueProto.Value.getDefaultInstance()) + .build())) + .collect(Collectors.toList()); + + return FeatureRow.newBuilder() + .setEventTimestamp(featureRow.getEventTimestamp()) + .addAllFields(values) + .build() + .toByteArray(); + } + + @ProcessElement + public void processElement(ProcessContext context) { + FeatureRow featureRow = context.element(); + featureRows.add(featureRow); + if (featureRows.size() >= batchSize) { + try { + executeBatch(); + featureRows.forEach(row -> context.output(successfulInsertsTag, row)); + featureRows.clear(); + } catch (Exception e) { + featureRows.forEach( + failedMutation -> { + FailedElement failedElement = + toFailedElement(failedMutation, e, context.getPipelineOptions().getJobName()); + context.output(failedInsertsTupleTag, failedElement); + }); + featureRows.clear(); + } + } + } + + @FinishBundle + public void finishBundle(FinishBundleContext context) + throws IOException, InterruptedException { + if (featureRows.size() > 0) { + try { + executeBatch(); + featureRows.forEach( + row -> + context.output( + successfulInsertsTag, row, Instant.now(), GlobalWindow.INSTANCE)); + featureRows.clear(); + } catch (Exception e) { + featureRows.forEach( + failedMutation -> { + FailedElement failedElement = + toFailedElement(failedMutation, e, context.getPipelineOptions().getJobName()); + context.output( + failedInsertsTupleTag, failedElement, Instant.now(), GlobalWindow.INSTANCE); + }); + featureRows.clear(); + } + } + } + + @Teardown + public void teardown() { + redisIngestionClient.shutdown(); + } + } + } +} diff --git a/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/writer/RedisFeatureSink.java b/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/writer/RedisFeatureSink.java new file mode 100644 index 00000000000..171f17daac2 --- /dev/null +++ b/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/writer/RedisFeatureSink.java @@ -0,0 +1,88 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.storage.connectors.redis.writer; + +import com.google.auto.value.AutoValue; +import feast.proto.core.FeatureSetProto.FeatureSet; +import feast.proto.core.FeatureSetProto.FeatureSetSpec; +import feast.proto.core.StoreProto; +import feast.proto.core.StoreProto.Store.RedisConfig; +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.storage.api.writer.FeatureSink; +import feast.storage.api.writer.WriteResult; +import io.lettuce.core.RedisClient; +import io.lettuce.core.RedisConnectionException; +import io.lettuce.core.RedisURI; +import java.util.Map; +import org.apache.beam.sdk.transforms.PTransform; +import org.apache.beam.sdk.values.PCollection; + +@AutoValue +public abstract class RedisFeatureSink implements FeatureSink { + + /** + * Initialize a {@link RedisFeatureSink.Builder} from a {@link StoreProto.Store.RedisConfig}. + * + * @param redisConfig {@link RedisConfig} + * @param featureSetSpecs + * @return {@link RedisFeatureSink.Builder} + */ + public static FeatureSink fromConfig( + RedisConfig redisConfig, Map featureSetSpecs) { + return builder().setFeatureSetSpecs(featureSetSpecs).setRedisConfig(redisConfig).build(); + } + + public abstract RedisConfig getRedisConfig(); + + public abstract Map getFeatureSetSpecs(); + + public abstract Builder toBuilder(); + + public static Builder builder() { + return new AutoValue_RedisFeatureSink.Builder(); + } + + @AutoValue.Builder + public abstract static class Builder { + public abstract Builder setRedisConfig(RedisConfig redisConfig); + + public abstract Builder setFeatureSetSpecs(Map featureSetSpecs); + + public abstract RedisFeatureSink build(); + } + + @Override + public void prepareWrite(FeatureSet featureSet) { + + RedisClient redisClient = + RedisClient.create(RedisURI.create(getRedisConfig().getHost(), getRedisConfig().getPort())); + try { + redisClient.connect(); + } catch (RedisConnectionException e) { + throw new RuntimeException( + String.format( + "Failed to connect to Redis at host: '%s' port: '%d'. Please check that your Redis is running and accessible from Feast.", + getRedisConfig().getHost(), getRedisConfig().getPort())); + } + redisClient.shutdown(); + } + + @Override + public PTransform, WriteResult> writer() { + return new RedisCustomIO.Write(getRedisConfig(), getFeatureSetSpecs()); + } +} diff --git a/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/writer/RedisIngestionClient.java b/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/writer/RedisIngestionClient.java new file mode 100644 index 00000000000..6616a79aaca --- /dev/null +++ b/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/writer/RedisIngestionClient.java @@ -0,0 +1,49 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.storage.connectors.redis.writer; + +import feast.storage.common.retry.BackOffExecutor; +import java.io.Serializable; + +public interface RedisIngestionClient extends Serializable { + + void setup(); + + BackOffExecutor getBackOffExecutor(); + + void shutdown(); + + void connect(); + + boolean isConnected(); + + void sync(); + + void pexpire(byte[] key, Long expiryMillis); + + void append(byte[] key, byte[] value); + + void set(byte[] key, byte[] value); + + void lpush(byte[] key, byte[] value); + + void rpush(byte[] key, byte[] value); + + void sadd(byte[] key, byte[] value); + + void zadd(byte[] key, Long score, byte[] value); +} diff --git a/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/writer/RedisStandaloneIngestionClient.java b/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/writer/RedisStandaloneIngestionClient.java new file mode 100644 index 00000000000..24591a1dc0f --- /dev/null +++ b/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/writer/RedisStandaloneIngestionClient.java @@ -0,0 +1,125 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.storage.connectors.redis.writer; + +import com.google.common.collect.Lists; +import feast.proto.core.StoreProto; +import feast.storage.common.retry.BackOffExecutor; +import io.lettuce.core.LettuceFutures; +import io.lettuce.core.RedisClient; +import io.lettuce.core.RedisFuture; +import io.lettuce.core.RedisURI; +import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.core.api.async.RedisAsyncCommands; +import io.lettuce.core.codec.ByteArrayCodec; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.joda.time.Duration; + +public class RedisStandaloneIngestionClient implements RedisIngestionClient { + private final String host; + private final int port; + private final BackOffExecutor backOffExecutor; + private RedisClient redisclient; + private static final int DEFAULT_TIMEOUT = 2000; + private StatefulRedisConnection connection; + private RedisAsyncCommands commands; + private List futures = Lists.newArrayList(); + + public RedisStandaloneIngestionClient(StoreProto.Store.RedisConfig redisConfig) { + this.host = redisConfig.getHost(); + this.port = redisConfig.getPort(); + long backoffMs = redisConfig.getInitialBackoffMs() > 0 ? redisConfig.getInitialBackoffMs() : 1; + this.backOffExecutor = + new BackOffExecutor(redisConfig.getMaxRetries(), Duration.millis(backoffMs)); + } + + @Override + public void setup() { + this.redisclient = + RedisClient.create(new RedisURI(host, port, java.time.Duration.ofMillis(DEFAULT_TIMEOUT))); + } + + @Override + public BackOffExecutor getBackOffExecutor() { + return this.backOffExecutor; + } + + @Override + public void shutdown() { + this.redisclient.shutdown(); + } + + @Override + public void connect() { + if (!isConnected()) { + this.connection = this.redisclient.connect(new ByteArrayCodec()); + this.commands = connection.async(); + } + } + + @Override + public boolean isConnected() { + return connection != null; + } + + @Override + public void sync() { + // Wait for some time for futures to complete + // TODO: should this be configurable? + try { + LettuceFutures.awaitAll(60, TimeUnit.SECONDS, futures.toArray(new RedisFuture[0])); + } finally { + futures.clear(); + } + } + + @Override + public void pexpire(byte[] key, Long expiryMillis) { + commands.pexpire(key, expiryMillis); + } + + @Override + public void append(byte[] key, byte[] value) { + futures.add(commands.append(key, value)); + } + + @Override + public void set(byte[] key, byte[] value) { + futures.add(commands.set(key, value)); + } + + @Override + public void lpush(byte[] key, byte[] value) { + futures.add(commands.lpush(key, value)); + } + + @Override + public void rpush(byte[] key, byte[] value) { + futures.add(commands.rpush(key, value)); + } + + @Override + public void sadd(byte[] key, byte[] value) { + futures.add(commands.sadd(key, value)); + } + + @Override + public void zadd(byte[] key, Long score, byte[] value) { + futures.add(commands.zadd(key, score, value)); + } +} diff --git a/storage/connectors/redis/src/test/java/feast/storage/connectors/redis/retriever/FeatureRowDecoderTest.java b/storage/connectors/redis/src/test/java/feast/storage/connectors/redis/retriever/FeatureRowDecoderTest.java new file mode 100644 index 00000000000..63ad7aa26de --- /dev/null +++ b/storage/connectors/redis/src/test/java/feast/storage/connectors/redis/retriever/FeatureRowDecoderTest.java @@ -0,0 +1,110 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.storage.connectors.redis.retriever; + +import static org.junit.Assert.*; + +import com.google.protobuf.Timestamp; +import feast.proto.core.FeatureSetProto; +import feast.proto.core.FeatureSetProto.FeatureSetSpec; +import feast.proto.types.FeatureRowProto; +import feast.proto.types.FieldProto.Field; +import feast.proto.types.ValueProto.Value; +import feast.proto.types.ValueProto.ValueType; +import java.util.Collections; +import org.junit.Test; + +public class FeatureRowDecoderTest { + + private FeatureSetProto.EntitySpec entity = + FeatureSetProto.EntitySpec.newBuilder().setName("entity1").build(); + + private FeatureSetSpec spec = + FeatureSetSpec.newBuilder() + .addAllEntities(Collections.singletonList(entity)) + .addFeatures( + FeatureSetProto.FeatureSpec.newBuilder() + .setName("feature1") + .setValueType(ValueType.Enum.FLOAT)) + .addFeatures( + FeatureSetProto.FeatureSpec.newBuilder() + .setName("feature2") + .setValueType(ValueType.Enum.INT32)) + .setName("feature_set_name") + .build(); + + @Test + public void featureRowWithFieldNamesIsNotConsideredAsEncoded() { + + FeatureRowDecoder decoder = new FeatureRowDecoder("feature_set_ref", spec); + FeatureRowProto.FeatureRow nonEncodedFeatureRow = + FeatureRowProto.FeatureRow.newBuilder() + .setFeatureSet("feature_set_ref") + .setEventTimestamp(Timestamp.newBuilder().setNanos(1000)) + .addFields( + Field.newBuilder().setName("feature1").setValue(Value.newBuilder().setInt32Val(2))) + .addFields( + Field.newBuilder() + .setName("feature2") + .setValue(Value.newBuilder().setFloatVal(1.0f))) + .build(); + assertFalse(decoder.isEncoded(nonEncodedFeatureRow)); + } + + @Test + public void encodingIsInvalidIfNumberOfFeaturesInSpecDiffersFromFeatureRow() { + + FeatureRowDecoder decoder = new FeatureRowDecoder("feature_set_ref", spec); + + FeatureRowProto.FeatureRow encodedFeatureRow = + FeatureRowProto.FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setNanos(1000)) + .addFields(Field.newBuilder().setValue(Value.newBuilder().setInt32Val(2))) + .build(); + + assertFalse(decoder.isEncodingValid(encodedFeatureRow)); + } + + @Test + public void shouldDecodeValidEncodedFeatureRow() { + + FeatureRowDecoder decoder = new FeatureRowDecoder("feature_set_ref", spec); + + FeatureRowProto.FeatureRow encodedFeatureRow = + FeatureRowProto.FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setNanos(1000)) + .addFields(Field.newBuilder().setValue(Value.newBuilder().setInt32Val(2))) + .addFields(Field.newBuilder().setValue(Value.newBuilder().setFloatVal(1.0f))) + .build(); + + FeatureRowProto.FeatureRow expectedFeatureRow = + FeatureRowProto.FeatureRow.newBuilder() + .setFeatureSet("feature_set_ref") + .setEventTimestamp(Timestamp.newBuilder().setNanos(1000)) + .addFields( + Field.newBuilder().setName("feature1").setValue(Value.newBuilder().setInt32Val(2))) + .addFields( + Field.newBuilder() + .setName("feature2") + .setValue(Value.newBuilder().setFloatVal(1.0f))) + .build(); + + assertTrue(decoder.isEncoded(encodedFeatureRow)); + assertTrue(decoder.isEncodingValid(encodedFeatureRow)); + assertEquals(expectedFeatureRow, decoder.decode(encodedFeatureRow)); + } +} diff --git a/storage/connectors/redis/src/test/java/feast/storage/connectors/redis/retriever/RedisOnlineRetrieverTest.java b/storage/connectors/redis/src/test/java/feast/storage/connectors/redis/retriever/RedisOnlineRetrieverTest.java new file mode 100644 index 00000000000..4ab2ac9e130 --- /dev/null +++ b/storage/connectors/redis/src/test/java/feast/storage/connectors/redis/retriever/RedisOnlineRetrieverTest.java @@ -0,0 +1,246 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.storage.connectors.redis.retriever; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.protobuf.AbstractMessageLite; +import com.google.protobuf.Duration; +import com.google.protobuf.Timestamp; +import feast.proto.core.FeatureSetProto.EntitySpec; +import feast.proto.core.FeatureSetProto.FeatureSetSpec; +import feast.proto.core.FeatureSetProto.FeatureSpec; +import feast.proto.serving.ServingAPIProto.FeatureReference; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequest.EntityRow; +import feast.proto.storage.RedisProto.RedisKey; +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.proto.types.FieldProto.Field; +import feast.proto.types.ValueProto.Value; +import feast.storage.api.retriever.FeatureSetRequest; +import feast.storage.api.retriever.OnlineRetriever; +import io.lettuce.core.KeyValue; +import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.core.api.sync.RedisCommands; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; + +public class RedisOnlineRetrieverTest { + + @Mock StatefulRedisConnection connection; + + @Mock RedisCommands syncCommands; + + private OnlineRetriever redisOnlineRetriever; + private byte[][] redisKeyList; + + @Before + public void setUp() { + initMocks(this); + when(connection.sync()).thenReturn(syncCommands); + redisOnlineRetriever = RedisOnlineRetriever.create(connection); + redisKeyList = + Lists.newArrayList( + RedisKey.newBuilder() + .setFeatureSet("project/featureSet") + .addAllEntities( + Lists.newArrayList( + Field.newBuilder().setName("entity1").setValue(intValue(1)).build(), + Field.newBuilder().setName("entity2").setValue(strValue("a")).build())) + .build(), + RedisKey.newBuilder() + .setFeatureSet("project/featureSet") + .addAllEntities( + Lists.newArrayList( + Field.newBuilder().setName("entity1").setValue(intValue(2)).build(), + Field.newBuilder().setName("entity2").setValue(strValue("b")).build())) + .build()) + .stream() + .map(AbstractMessageLite::toByteArray) + .collect(Collectors.toList()) + .toArray(new byte[0][0]); + } + + @Test + public void shouldReturnResponseWithValuesIfKeysPresent() { + FeatureSetRequest featureSetRequest = + FeatureSetRequest.newBuilder() + .setSpec(getFeatureSetSpec()) + .addFeatureReference( + FeatureReference.newBuilder().setName("feature1").setProject("project").build()) + .addFeatureReference( + FeatureReference.newBuilder().setName("feature2").setProject("project").build()) + .build(); + List entityRows = + ImmutableList.of( + EntityRow.newBuilder() + .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) + .putFields("entity1", intValue(1)) + .putFields("entity2", strValue("a")) + .build(), + EntityRow.newBuilder() + .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) + .putFields("entity1", intValue(2)) + .putFields("entity2", strValue("b")) + .build()); + + List featureRows = + Lists.newArrayList( + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setSeconds(100)) + .addAllFields( + Lists.newArrayList( + Field.newBuilder().setValue(intValue(1)).build(), + Field.newBuilder().setValue(intValue(1)).build())) + .build(), + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setSeconds(100)) + .addAllFields( + Lists.newArrayList( + Field.newBuilder().setValue(intValue(2)).build(), + Field.newBuilder().setValue(intValue(2)).build())) + .build()); + + List> featureRowBytes = + featureRows.stream() + .map(x -> KeyValue.from(new byte[1], Optional.of(x.toByteArray()))) + .collect(Collectors.toList()); + + redisOnlineRetriever = RedisOnlineRetriever.create(connection); + when(connection.sync()).thenReturn(syncCommands); + when(syncCommands.mget(redisKeyList)).thenReturn(featureRowBytes); + + List> expected = + List.of( + Lists.newArrayList( + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setSeconds(100)) + .setFeatureSet("project/featureSet") + .addAllFields( + Lists.newArrayList( + Field.newBuilder().setName("feature1").setValue(intValue(1)).build(), + Field.newBuilder().setName("feature2").setValue(intValue(1)).build())) + .build(), + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setSeconds(100)) + .setFeatureSet("project/featureSet") + .addAllFields( + Lists.newArrayList( + Field.newBuilder().setName("feature1").setValue(intValue(2)).build(), + Field.newBuilder().setName("feature2").setValue(intValue(2)).build())) + .build())); + + List> actual = + redisOnlineRetriever.getOnlineFeatures(entityRows, List.of(featureSetRequest)); + assertThat(actual, equalTo(expected)); + } + + @Test + public void shouldReturnResponseWithUnsetValuesIfKeysNotPresent() { + FeatureSetRequest featureSetRequest = + FeatureSetRequest.newBuilder() + .setSpec(getFeatureSetSpec()) + .addFeatureReference( + FeatureReference.newBuilder().setName("feature1").setProject("project").build()) + .addFeatureReference( + FeatureReference.newBuilder().setName("feature2").setProject("project").build()) + .build(); + List entityRows = + ImmutableList.of( + EntityRow.newBuilder() + .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) + .putFields("entity1", intValue(1)) + .putFields("entity2", strValue("a")) + .build(), + EntityRow.newBuilder() + .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) + .putFields("entity1", intValue(2)) + .putFields("entity2", strValue("b")) + .build()); + + List featureRows = + Lists.newArrayList( + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setSeconds(100)) + .addAllFields( + Lists.newArrayList( + Field.newBuilder().setValue(intValue(1)).build(), + Field.newBuilder().setValue(intValue(1)).build())) + .build()); + + List> featureRowBytes = + featureRows.stream() + .map(x -> KeyValue.from(new byte[1], Optional.of(x.toByteArray()))) + .collect(Collectors.toList()); + featureRowBytes.add(null); + + redisOnlineRetriever = RedisOnlineRetriever.create(connection); + when(connection.sync()).thenReturn(syncCommands); + when(syncCommands.mget(redisKeyList)).thenReturn(featureRowBytes); + + List> expected = + List.of( + Lists.newArrayList( + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setSeconds(100)) + .setFeatureSet("project/featureSet") + .addAllFields( + Lists.newArrayList( + Field.newBuilder().setName("feature1").setValue(intValue(1)).build(), + Field.newBuilder().setName("feature2").setValue(intValue(1)).build())) + .build(), + FeatureRow.newBuilder() + .setFeatureSet("project/featureSet") + .addAllFields( + Lists.newArrayList( + Field.newBuilder().setName("feature1").build(), + Field.newBuilder().setName("feature2").build())) + .build())); + + List> actual = + redisOnlineRetriever.getOnlineFeatures(entityRows, List.of(featureSetRequest)); + assertThat(actual, equalTo(expected)); + } + + private Value intValue(int val) { + return Value.newBuilder().setInt64Val(val).build(); + } + + private Value strValue(String val) { + return Value.newBuilder().setStringVal(val).build(); + } + + private FeatureSetSpec getFeatureSetSpec() { + return FeatureSetSpec.newBuilder() + .setProject("project") + .setName("featureSet") + .addEntities(EntitySpec.newBuilder().setName("entity1")) + .addEntities(EntitySpec.newBuilder().setName("entity2")) + .addFeatures(FeatureSpec.newBuilder().setName("feature1")) + .addFeatures(FeatureSpec.newBuilder().setName("feature2")) + .setMaxAge(Duration.newBuilder().setSeconds(30)) // default + .build(); + } +} diff --git a/storage/connectors/redis/src/test/java/feast/storage/connectors/redis/test/TestUtil.java b/storage/connectors/redis/src/test/java/feast/storage/connectors/redis/test/TestUtil.java new file mode 100644 index 00000000000..66aba44bc20 --- /dev/null +++ b/storage/connectors/redis/src/test/java/feast/storage/connectors/redis/test/TestUtil.java @@ -0,0 +1,44 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.storage.connectors.redis.test; + +import java.io.IOException; +import redis.embedded.RedisServer; + +public class TestUtil { + public static class LocalRedis { + + private static RedisServer server; + + /** + * Start local Redis for used in testing at "localhost" + * + * @param port port number + * @throws IOException if Redis failed to start + */ + public static void start(int port) throws IOException { + server = new RedisServer(port); + server.start(); + } + + public static void stop() { + if (server != null) { + server.stop(); + } + } + } +} diff --git a/storage/connectors/redis/src/test/java/feast/storage/connectors/redis/writer/RedisFeatureSinkTest.java b/storage/connectors/redis/src/test/java/feast/storage/connectors/redis/writer/RedisFeatureSinkTest.java new file mode 100644 index 00000000000..0da4c102cd4 --- /dev/null +++ b/storage/connectors/redis/src/test/java/feast/storage/connectors/redis/writer/RedisFeatureSinkTest.java @@ -0,0 +1,477 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2019 The Feast Authors + * + * 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 feast.storage.connectors.redis.writer; + +import static feast.storage.common.testing.TestUtil.field; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.protobuf.Timestamp; +import feast.proto.core.FeatureSetProto.EntitySpec; +import feast.proto.core.FeatureSetProto.FeatureSetSpec; +import feast.proto.core.FeatureSetProto.FeatureSpec; +import feast.proto.core.StoreProto; +import feast.proto.core.StoreProto.Store.RedisConfig; +import feast.proto.storage.RedisProto.RedisKey; +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.proto.types.FieldProto.Field; +import feast.proto.types.ValueProto.Value; +import feast.proto.types.ValueProto.ValueType.Enum; +import io.lettuce.core.RedisClient; +import io.lettuce.core.RedisURI; +import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.core.api.sync.RedisStringCommands; +import io.lettuce.core.codec.ByteArrayCodec; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import org.apache.beam.sdk.testing.PAssert; +import org.apache.beam.sdk.testing.TestPipeline; +import org.apache.beam.sdk.transforms.Count; +import org.apache.beam.sdk.transforms.Create; +import org.apache.beam.sdk.values.PCollection; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import redis.embedded.Redis; +import redis.embedded.RedisServer; + +public class RedisFeatureSinkTest { + @Rule public transient TestPipeline p = TestPipeline.create(); + + private static String REDIS_HOST = "localhost"; + private static int REDIS_PORT = 51234; + private Redis redis; + private RedisClient redisClient; + private RedisStringCommands sync; + + private RedisFeatureSink redisFeatureSink; + + @Before + public void setUp() throws IOException { + redis = new RedisServer(REDIS_PORT); + redis.start(); + redisClient = + RedisClient.create(new RedisURI(REDIS_HOST, REDIS_PORT, java.time.Duration.ofMillis(2000))); + StatefulRedisConnection connection = redisClient.connect(new ByteArrayCodec()); + sync = connection.sync(); + + FeatureSetSpec spec1 = + FeatureSetSpec.newBuilder() + .setName("fs") + .setProject("myproject") + .addEntities(EntitySpec.newBuilder().setName("entity").setValueType(Enum.INT64).build()) + .addFeatures( + FeatureSpec.newBuilder().setName("feature").setValueType(Enum.STRING).build()) + .build(); + + FeatureSetSpec spec2 = + FeatureSetSpec.newBuilder() + .setName("feature_set") + .setProject("myproject") + .addEntities( + EntitySpec.newBuilder() + .setName("entity_id_primary") + .setValueType(Enum.INT32) + .build()) + .addEntities( + EntitySpec.newBuilder() + .setName("entity_id_secondary") + .setValueType(Enum.STRING) + .build()) + .addFeatures( + FeatureSpec.newBuilder().setName("feature_1").setValueType(Enum.STRING).build()) + .addFeatures( + FeatureSpec.newBuilder().setName("feature_2").setValueType(Enum.INT64).build()) + .build(); + + Map specMap = + ImmutableMap.of("myproject/fs", spec1, "myproject/feature_set", spec2); + StoreProto.Store.RedisConfig redisConfig = + StoreProto.Store.RedisConfig.newBuilder().setHost(REDIS_HOST).setPort(REDIS_PORT).build(); + + redisFeatureSink = + RedisFeatureSink.builder().setFeatureSetSpecs(specMap).setRedisConfig(redisConfig).build(); + } + + @After + public void teardown() { + redisClient.shutdown(); + redis.stop(); + } + + @Test + public void shouldWriteToRedis() { + + HashMap kvs = new LinkedHashMap<>(); + kvs.put( + RedisKey.newBuilder() + .setFeatureSet("myproject/fs") + .addEntities(field("entity", 1, Enum.INT64)) + .build(), + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.getDefaultInstance()) + .addFields(Field.newBuilder().setValue(Value.newBuilder().setStringVal("one"))) + .build()); + kvs.put( + RedisKey.newBuilder() + .setFeatureSet("myproject/fs") + .addEntities(field("entity", 2, Enum.INT64)) + .build(), + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.getDefaultInstance()) + .addFields(Field.newBuilder().setValue(Value.newBuilder().setStringVal("two"))) + .build()); + + List featureRows = + ImmutableList.of( + FeatureRow.newBuilder() + .setFeatureSet("myproject/fs") + .addFields(field("entity", 1, Enum.INT64)) + .addFields(field("feature", "one", Enum.STRING)) + .build(), + FeatureRow.newBuilder() + .setFeatureSet("myproject/fs") + .addFields(field("entity", 2, Enum.INT64)) + .addFields(field("feature", "two", Enum.STRING)) + .build()); + + p.apply(Create.of(featureRows)).apply(redisFeatureSink.writer()); + p.run(); + + kvs.forEach( + (key, value) -> { + byte[] actual = sync.get(key.toByteArray()); + assertThat(actual, equalTo(value.toByteArray())); + }); + } + + @Test(timeout = 10000) + public void shouldRetryFailConnection() throws InterruptedException { + RedisConfig redisConfig = + RedisConfig.newBuilder() + .setHost(REDIS_HOST) + .setPort(REDIS_PORT) + .setMaxRetries(4) + .setInitialBackoffMs(2000) + .build(); + redisFeatureSink = redisFeatureSink.toBuilder().setRedisConfig(redisConfig).build(); + + HashMap kvs = new LinkedHashMap<>(); + kvs.put( + RedisKey.newBuilder() + .setFeatureSet("myproject/fs") + .addEntities(field("entity", 1, Enum.INT64)) + .build(), + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.getDefaultInstance()) + .addFields(Field.newBuilder().setValue(Value.newBuilder().setStringVal("one"))) + .build()); + + List featureRows = + ImmutableList.of( + FeatureRow.newBuilder() + .setFeatureSet("myproject/fs") + .addFields(field("entity", 1, Enum.INT64)) + .addFields(field("feature", "one", Enum.STRING)) + .build()); + + PCollection failedElementCount = + p.apply(Create.of(featureRows)) + .apply(redisFeatureSink.writer()) + .getFailedInserts() + .apply(Count.globally()); + + redis.stop(); + final ScheduledThreadPoolExecutor redisRestartExecutor = new ScheduledThreadPoolExecutor(1); + ScheduledFuture scheduledRedisRestart = + redisRestartExecutor.schedule( + () -> { + redis.start(); + }, + 3, + TimeUnit.SECONDS); + + PAssert.that(failedElementCount).containsInAnyOrder(0L); + p.run(); + scheduledRedisRestart.cancel(true); + + kvs.forEach( + (key, value) -> { + byte[] actual = sync.get(key.toByteArray()); + assertThat(actual, equalTo(value.toByteArray())); + }); + } + + @Test + public void shouldProduceFailedElementIfRetryExceeded() { + + RedisConfig redisConfig = + RedisConfig.newBuilder().setHost(REDIS_HOST).setPort(REDIS_PORT + 1).build(); + redisFeatureSink = redisFeatureSink.toBuilder().setRedisConfig(redisConfig).build(); + + HashMap kvs = new LinkedHashMap<>(); + kvs.put( + RedisKey.newBuilder() + .setFeatureSet("myproject/fs") + .addEntities(field("entity", 1, Enum.INT64)) + .build(), + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.getDefaultInstance()) + .addFields(Field.newBuilder().setValue(Value.newBuilder().setStringVal("one"))) + .build()); + + List featureRows = + ImmutableList.of( + FeatureRow.newBuilder() + .setFeatureSet("myproject/fs") + .addFields(field("entity", 1, Enum.INT64)) + .addFields(field("feature", "one", Enum.STRING)) + .build()); + + PCollection failedElementCount = + p.apply(Create.of(featureRows)) + .apply(redisFeatureSink.writer()) + .getFailedInserts() + .apply(Count.globally()); + + redis.stop(); + PAssert.that(failedElementCount).containsInAnyOrder(1L); + p.run(); + } + + @Test + public void shouldConvertRowWithDuplicateEntitiesToValidKey() { + + FeatureRow offendingRow = + FeatureRow.newBuilder() + .setFeatureSet("myproject/feature_set") + .setEventTimestamp(Timestamp.newBuilder().setSeconds(10)) + .addFields( + Field.newBuilder() + .setName("entity_id_primary") + .setValue(Value.newBuilder().setInt32Val(1))) + .addFields( + Field.newBuilder() + .setName("entity_id_primary") + .setValue(Value.newBuilder().setInt32Val(2))) + .addFields( + Field.newBuilder() + .setName("entity_id_secondary") + .setValue(Value.newBuilder().setStringVal("a"))) + .addFields( + Field.newBuilder() + .setName("feature_1") + .setValue(Value.newBuilder().setStringVal("strValue1"))) + .addFields( + Field.newBuilder() + .setName("feature_2") + .setValue(Value.newBuilder().setInt64Val(1001))) + .build(); + + RedisKey expectedKey = + RedisKey.newBuilder() + .setFeatureSet("myproject/feature_set") + .addEntities( + Field.newBuilder() + .setName("entity_id_primary") + .setValue(Value.newBuilder().setInt32Val(1))) + .addEntities( + Field.newBuilder() + .setName("entity_id_secondary") + .setValue(Value.newBuilder().setStringVal("a"))) + .build(); + + FeatureRow expectedValue = + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setSeconds(10)) + .addFields(Field.newBuilder().setValue(Value.newBuilder().setStringVal("strValue1"))) + .addFields(Field.newBuilder().setValue(Value.newBuilder().setInt64Val(1001))) + .build(); + + p.apply(Create.of(offendingRow)).apply(redisFeatureSink.writer()); + + p.run(); + + byte[] actual = sync.get(expectedKey.toByteArray()); + assertThat(actual, equalTo(expectedValue.toByteArray())); + } + + @Test + public void shouldConvertRowWithOutOfOrderFieldsToValidKey() { + FeatureRow offendingRow = + FeatureRow.newBuilder() + .setFeatureSet("myproject/feature_set") + .setEventTimestamp(Timestamp.newBuilder().setSeconds(10)) + .addFields( + Field.newBuilder() + .setName("entity_id_secondary") + .setValue(Value.newBuilder().setStringVal("a"))) + .addFields( + Field.newBuilder() + .setName("entity_id_primary") + .setValue(Value.newBuilder().setInt32Val(1))) + .addFields( + Field.newBuilder() + .setName("feature_2") + .setValue(Value.newBuilder().setInt64Val(1001))) + .addFields( + Field.newBuilder() + .setName("feature_1") + .setValue(Value.newBuilder().setStringVal("strValue1"))) + .build(); + + RedisKey expectedKey = + RedisKey.newBuilder() + .setFeatureSet("myproject/feature_set") + .addEntities( + Field.newBuilder() + .setName("entity_id_primary") + .setValue(Value.newBuilder().setInt32Val(1))) + .addEntities( + Field.newBuilder() + .setName("entity_id_secondary") + .setValue(Value.newBuilder().setStringVal("a"))) + .build(); + + List expectedFields = + Arrays.asList( + Field.newBuilder().setValue(Value.newBuilder().setStringVal("strValue1")).build(), + Field.newBuilder().setValue(Value.newBuilder().setInt64Val(1001)).build()); + FeatureRow expectedValue = + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setSeconds(10)) + .addAllFields(expectedFields) + .build(); + + p.apply(Create.of(offendingRow)).apply(redisFeatureSink.writer()); + + p.run(); + + byte[] actual = sync.get(expectedKey.toByteArray()); + assertThat(actual, equalTo(expectedValue.toByteArray())); + } + + @Test + public void shouldMergeDuplicateFeatureFields() { + FeatureRow featureRowWithDuplicatedFeatureFields = + FeatureRow.newBuilder() + .setFeatureSet("myproject/feature_set") + .setEventTimestamp(Timestamp.newBuilder().setSeconds(10)) + .addFields( + Field.newBuilder() + .setName("entity_id_primary") + .setValue(Value.newBuilder().setInt32Val(1))) + .addFields( + Field.newBuilder() + .setName("entity_id_secondary") + .setValue(Value.newBuilder().setStringVal("a"))) + .addFields( + Field.newBuilder() + .setName("feature_1") + .setValue(Value.newBuilder().setStringVal("strValue1"))) + .addFields( + Field.newBuilder() + .setName("feature_1") + .setValue(Value.newBuilder().setStringVal("strValue1"))) + .addFields( + Field.newBuilder() + .setName("feature_2") + .setValue(Value.newBuilder().setInt64Val(1001))) + .build(); + + RedisKey expectedKey = + RedisKey.newBuilder() + .setFeatureSet("myproject/feature_set") + .addEntities( + Field.newBuilder() + .setName("entity_id_primary") + .setValue(Value.newBuilder().setInt32Val(1))) + .addEntities( + Field.newBuilder() + .setName("entity_id_secondary") + .setValue(Value.newBuilder().setStringVal("a"))) + .build(); + + FeatureRow expectedValue = + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setSeconds(10)) + .addFields(Field.newBuilder().setValue(Value.newBuilder().setStringVal("strValue1"))) + .addFields(Field.newBuilder().setValue(Value.newBuilder().setInt64Val(1001))) + .build(); + + p.apply(Create.of(featureRowWithDuplicatedFeatureFields)).apply(redisFeatureSink.writer()); + + p.run(); + + byte[] actual = sync.get(expectedKey.toByteArray()); + assertThat(actual, equalTo(expectedValue.toByteArray())); + } + + @Test + public void shouldPopulateMissingFeatureValuesWithDefaultInstance() { + FeatureRow featureRowWithDuplicatedFeatureFields = + FeatureRow.newBuilder() + .setFeatureSet("myproject/feature_set") + .setEventTimestamp(Timestamp.newBuilder().setSeconds(10)) + .addFields( + Field.newBuilder() + .setName("entity_id_primary") + .setValue(Value.newBuilder().setInt32Val(1))) + .addFields( + Field.newBuilder() + .setName("entity_id_secondary") + .setValue(Value.newBuilder().setStringVal("a"))) + .addFields( + Field.newBuilder() + .setName("feature_1") + .setValue(Value.newBuilder().setStringVal("strValue1"))) + .build(); + + RedisKey expectedKey = + RedisKey.newBuilder() + .setFeatureSet("myproject/feature_set") + .addEntities( + Field.newBuilder() + .setName("entity_id_primary") + .setValue(Value.newBuilder().setInt32Val(1))) + .addEntities( + Field.newBuilder() + .setName("entity_id_secondary") + .setValue(Value.newBuilder().setStringVal("a"))) + .build(); + + FeatureRow expectedValue = + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setSeconds(10)) + .addFields(Field.newBuilder().setValue(Value.newBuilder().setStringVal("strValue1"))) + .addFields(Field.newBuilder().setValue(Value.getDefaultInstance())) + .build(); + + p.apply(Create.of(featureRowWithDuplicatedFeatureFields)).apply(redisFeatureSink.writer()); + + p.run(); + + byte[] actual = sync.get(expectedKey.toByteArray()); + assertThat(actual, equalTo(expectedValue.toByteArray())); + } +} diff --git a/storage/connectors/rediscluster/pom.xml b/storage/connectors/rediscluster/pom.xml new file mode 100644 index 00000000000..5c3cb6e42d3 --- /dev/null +++ b/storage/connectors/rediscluster/pom.xml @@ -0,0 +1,81 @@ + + + + dev.feast + feast-storage-connectors + ${revision} + + + 4.0.0 + feast-storage-connector-redis-cluster + + Feast Storage Connector for Redis Cluster + + + + io.lettuce + lettuce-core + + + + org.apache.commons + commons-lang3 + 3.9 + + + + com.google.auto.value + auto-value-annotations + 1.6.6 + + + + com.google.auto.value + auto-value + 1.6.6 + provided + + + + org.mockito + mockito-core + 2.23.0 + test + + + + org.apache.beam + beam-runners-direct-java + ${org.apache.beam.version} + test + + + + org.hamcrest + hamcrest-core + test + + + + org.hamcrest + hamcrest-library + test + + + + net.ishiis.redis + redis-unit + 1.0.3 + test + + + + junit + junit + 4.12 + test + + + + diff --git a/storage/connectors/rediscluster/src/main/java/feast/storage/connectors/rediscluster/retriever/FeatureRowDecoder.java b/storage/connectors/rediscluster/src/main/java/feast/storage/connectors/rediscluster/retriever/FeatureRowDecoder.java new file mode 100644 index 00000000000..d398752aa47 --- /dev/null +++ b/storage/connectors/rediscluster/src/main/java/feast/storage/connectors/rediscluster/retriever/FeatureRowDecoder.java @@ -0,0 +1,82 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.storage.connectors.rediscluster.retriever; + +import feast.proto.core.FeatureSetProto.FeatureSetSpec; +import feast.proto.core.FeatureSetProto.FeatureSpec; +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.proto.types.FieldProto.Field; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class FeatureRowDecoder { + + private final String featureSetRef; + private final FeatureSetSpec spec; + + public FeatureRowDecoder(String featureSetRef, FeatureSetSpec spec) { + this.featureSetRef = featureSetRef; + this.spec = spec; + } + + /** + * Validates if an encoded feature row can be decoded without exception. + * + * @param featureRow Feature row + * @return boolean + */ + public Boolean isEncodingValid(FeatureRow featureRow) { + return featureRow.getFieldsList().size() == spec.getFeaturesList().size(); + } + + /** + * Decoding feature row by repopulating the field names based on the corresponding feature set + * spec. + * + * @param encodedFeatureRow Feature row + * @return boolean + */ + public FeatureRow decode(FeatureRow encodedFeatureRow) { + final List fieldsWithoutName = encodedFeatureRow.getFieldsList(); + + List featureNames = + spec.getFeaturesList().stream() + .sorted(Comparator.comparing(FeatureSpec::getName)) + .map(FeatureSpec::getName) + .collect(Collectors.toList()); + List fields = + IntStream.range(0, featureNames.size()) + .mapToObj( + featureNameIndex -> { + String featureName = featureNames.get(featureNameIndex); + return fieldsWithoutName + .get(featureNameIndex) + .toBuilder() + .setName(featureName) + .build(); + }) + .collect(Collectors.toList()); + return encodedFeatureRow + .toBuilder() + .clearFields() + .setFeatureSet(featureSetRef) + .addAllFields(fields) + .build(); + } +} diff --git a/storage/connectors/rediscluster/src/main/java/feast/storage/connectors/rediscluster/retriever/RedisClusterOnlineRetriever.java b/storage/connectors/rediscluster/src/main/java/feast/storage/connectors/rediscluster/retriever/RedisClusterOnlineRetriever.java new file mode 100644 index 00000000000..f80de0fc8af --- /dev/null +++ b/storage/connectors/rediscluster/src/main/java/feast/storage/connectors/rediscluster/retriever/RedisClusterOnlineRetriever.java @@ -0,0 +1,223 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.storage.connectors.rediscluster.retriever; + +import com.google.protobuf.AbstractMessageLite; +import com.google.protobuf.InvalidProtocolBufferException; +import feast.proto.core.FeatureSetProto.EntitySpec; +import feast.proto.core.FeatureSetProto.FeatureSetSpec; +import feast.proto.serving.ServingAPIProto.FeatureReference; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequest.EntityRow; +import feast.proto.storage.RedisProto.RedisKey; +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.proto.types.FieldProto.Field; +import feast.proto.types.ValueProto.Value; +import feast.storage.api.retriever.FeatureSetRequest; +import feast.storage.api.retriever.OnlineRetriever; +import io.grpc.Status; +import io.lettuce.core.RedisURI; +import io.lettuce.core.cluster.RedisClusterClient; +import io.lettuce.core.cluster.api.StatefulRedisClusterConnection; +import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands; +import io.lettuce.core.codec.ByteArrayCodec; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; + +public class RedisClusterOnlineRetriever implements OnlineRetriever { + + private final RedisAdvancedClusterCommands syncCommands; + + private RedisClusterOnlineRetriever(StatefulRedisClusterConnection connection) { + this.syncCommands = connection.sync(); + } + + public static OnlineRetriever create(Map config) { + List redisURIList = + Arrays.stream(config.get("connection_string").split(",")) + .map( + hostPort -> { + String[] hostPortSplit = hostPort.trim().split(":"); + return RedisURI.create(hostPortSplit[0], Integer.parseInt(hostPortSplit[1])); + }) + .collect(Collectors.toList()); + + StatefulRedisClusterConnection connection = + RedisClusterClient.create(redisURIList).connect(new ByteArrayCodec()); + + return new RedisClusterOnlineRetriever(connection); + } + + public static OnlineRetriever create(StatefulRedisClusterConnection connection) { + return new RedisClusterOnlineRetriever(connection); + } + + /** + * Gets online features from redis. This method returns a list of {@link FeatureRow}s + * corresponding to each feature set spec. Each feature row in the list then corresponds to an + * {@link EntityRow} provided by the user. + * + * @param entityRows list of entity rows in the feature request + * @param featureSetRequests Map of {@link FeatureSetSpec} to feature references in the request + * tied to that feature set. + * @return List of List of {@link FeatureRow} + */ + @Override + public List> getOnlineFeatures( + List entityRows, List featureSetRequests) { + + List> featureRows = new ArrayList<>(); + for (FeatureSetRequest featureSetRequest : featureSetRequests) { + List redisKeys = buildRedisKeys(entityRows, featureSetRequest.getSpec()); + try { + List featureRowsForFeatureSet = + sendAndProcessMultiGet( + redisKeys, + featureSetRequest.getSpec(), + featureSetRequest.getFeatureReferences().asList()); + featureRows.add(featureRowsForFeatureSet); + } catch (InvalidProtocolBufferException | ExecutionException e) { + throw Status.INTERNAL + .withDescription("Unable to parse protobuf while retrieving feature") + .withCause(e) + .asRuntimeException(); + } + } + return featureRows; + } + + private List buildRedisKeys(List entityRows, FeatureSetSpec featureSetSpec) { + String featureSetRef = generateFeatureSetStringRef(featureSetSpec); + List featureSetEntityNames = + featureSetSpec.getEntitiesList().stream() + .map(EntitySpec::getName) + .collect(Collectors.toList()); + List redisKeys = + entityRows.stream() + .map(row -> makeRedisKey(featureSetRef, featureSetEntityNames, row)) + .collect(Collectors.toList()); + return redisKeys; + } + + /** + * Create {@link RedisKey} + * + * @param featureSet featureSet reference of the feature. E.g. feature_set_1:1 + * @param featureSetEntityNames entity names that belong to the featureSet + * @param entityRow entityRow to build the key from + * @return {@link RedisKey} + */ + private RedisKey makeRedisKey( + String featureSet, List featureSetEntityNames, EntityRow entityRow) { + RedisKey.Builder builder = RedisKey.newBuilder().setFeatureSet(featureSet); + Map fieldsMap = entityRow.getFieldsMap(); + featureSetEntityNames.sort(String::compareTo); + for (int i = 0; i < featureSetEntityNames.size(); i++) { + String entityName = featureSetEntityNames.get(i); + + if (!fieldsMap.containsKey(entityName)) { + throw Status.INVALID_ARGUMENT + .withDescription( + String.format( + "Entity row fields \"%s\" does not contain required entity field \"%s\"", + fieldsMap.keySet().toString(), entityName)) + .asRuntimeException(); + } + + builder.addEntities( + Field.newBuilder().setName(entityName).setValue(fieldsMap.get(entityName))); + } + return builder.build(); + } + + private List sendAndProcessMultiGet( + List redisKeys, + FeatureSetSpec featureSetSpec, + List featureReferences) + throws InvalidProtocolBufferException, ExecutionException { + + List values = sendMultiGet(redisKeys); + List featureRows = new ArrayList<>(); + + FeatureRow.Builder nullFeatureRowBuilder = + FeatureRow.newBuilder().setFeatureSet(generateFeatureSetStringRef(featureSetSpec)); + for (FeatureReference featureReference : featureReferences) { + nullFeatureRowBuilder.addFields(Field.newBuilder().setName(featureReference.getName())); + } + + for (int i = 0; i < values.size(); i++) { + + byte[] value = values.get(i); + if (value == null) { + featureRows.add(nullFeatureRowBuilder.build()); + continue; + } + + FeatureRow featureRow = FeatureRow.parseFrom(value); + String featureSetRef = redisKeys.get(i).getFeatureSet(); + FeatureRowDecoder decoder = new FeatureRowDecoder(featureSetRef, featureSetSpec); + if (decoder.isEncodingValid(featureRow)) { + featureRow = decoder.decode(featureRow); + } else { + featureRows.add(nullFeatureRowBuilder.build()); + continue; + } + + featureRows.add(featureRow); + } + return featureRows; + } + + /** + * Send a list of get request as an mget + * + * @param keys list of {@link RedisKey} + * @return list of {@link FeatureRow} in primitive byte representation for each {@link RedisKey} + */ + private List sendMultiGet(List keys) { + try { + byte[][] binaryKeys = + keys.stream() + .map(AbstractMessageLite::toByteArray) + .collect(Collectors.toList()) + .toArray(new byte[0][0]); + return syncCommands.mget(binaryKeys).stream() + .map( + keyValue -> { + if (keyValue == null) { + return null; + } + return keyValue.getValueOrElse(null); + }) + .collect(Collectors.toList()); + } catch (Exception e) { + throw Status.NOT_FOUND + .withDescription("Unable to retrieve feature from Redis") + .withCause(e) + .asRuntimeException(); + } + } + + // TODO: Refactor this out to common package? + private static String generateFeatureSetStringRef(FeatureSetSpec featureSetSpec) { + String ref = String.format("%s/%s", featureSetSpec.getProject(), featureSetSpec.getName()); + return ref; + } +} diff --git a/storage/connectors/rediscluster/src/main/java/feast/storage/connectors/rediscluster/writer/RedisClusterCustomIO.java b/storage/connectors/rediscluster/src/main/java/feast/storage/connectors/rediscluster/writer/RedisClusterCustomIO.java new file mode 100644 index 00000000000..8984dc877cd --- /dev/null +++ b/storage/connectors/rediscluster/src/main/java/feast/storage/connectors/rediscluster/writer/RedisClusterCustomIO.java @@ -0,0 +1,294 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2019 The Feast Authors + * + * 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 feast.storage.connectors.rediscluster.writer; + +import feast.proto.core.FeatureSetProto.EntitySpec; +import feast.proto.core.FeatureSetProto.FeatureSetSpec; +import feast.proto.core.FeatureSetProto.FeatureSpec; +import feast.proto.core.StoreProto.Store.RedisClusterConfig; +import feast.proto.storage.RedisProto.RedisKey; +import feast.proto.storage.RedisProto.RedisKey.Builder; +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.proto.types.FieldProto.Field; +import feast.proto.types.ValueProto; +import feast.storage.api.writer.FailedElement; +import feast.storage.api.writer.WriteResult; +import feast.storage.common.retry.Retriable; +import io.lettuce.core.RedisException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; +import org.apache.beam.sdk.transforms.DoFn; +import org.apache.beam.sdk.transforms.PTransform; +import org.apache.beam.sdk.transforms.ParDo; +import org.apache.beam.sdk.transforms.windowing.GlobalWindow; +import org.apache.beam.sdk.values.PCollection; +import org.apache.beam.sdk.values.PCollectionTuple; +import org.apache.beam.sdk.values.TupleTag; +import org.apache.beam.sdk.values.TupleTagList; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.joda.time.Instant; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RedisClusterCustomIO { + + private static final int DEFAULT_BATCH_SIZE = 1000; + private static final int DEFAULT_TIMEOUT = 2000; + + private static TupleTag successfulInsertsTag = new TupleTag<>("successfulInserts") {}; + private static TupleTag failedInsertsTupleTag = new TupleTag<>("failedInserts") {}; + + private static final Logger log = LoggerFactory.getLogger(RedisClusterCustomIO.class); + + private RedisClusterCustomIO() {} + + public static Write write( + RedisClusterConfig redisClusterConfig, Map featureSetSpecs) { + return new Write(redisClusterConfig, featureSetSpecs); + } + + /** ServingStoreWrite data to a Redis server. */ + public static class Write extends PTransform, WriteResult> { + + private Map featureSetSpecs; + private RedisClusterConfig redisClusterConfig; + private int batchSize; + private int timeout; + + public Write( + RedisClusterConfig redisClusterConfig, Map featureSetSpecs) { + + this.redisClusterConfig = redisClusterConfig; + this.featureSetSpecs = featureSetSpecs; + } + + public Write withBatchSize(int batchSize) { + this.batchSize = batchSize; + return this; + } + + public Write withTimeout(int timeout) { + this.timeout = timeout; + return this; + } + + @Override + public WriteResult expand(PCollection input) { + PCollectionTuple redisWrite = + input.apply( + ParDo.of(new WriteDoFn(redisClusterConfig, featureSetSpecs)) + .withOutputTags(successfulInsertsTag, TupleTagList.of(failedInsertsTupleTag))); + return WriteResult.in( + input.getPipeline(), + redisWrite.get(successfulInsertsTag), + redisWrite.get(failedInsertsTupleTag)); + } + + public static class WriteDoFn extends DoFn { + + private final List featureRows = new ArrayList<>(); + private Map featureSetSpecs; + private int batchSize = DEFAULT_BATCH_SIZE; + private int timeout = DEFAULT_TIMEOUT; + private RedisIngestionClient redisIngestionClient; + + WriteDoFn(RedisClusterConfig config, Map featureSetSpecs) { + + this.redisIngestionClient = new RedisClusterIngestionClient(config); + this.featureSetSpecs = featureSetSpecs; + } + + public WriteDoFn withBatchSize(int batchSize) { + if (batchSize > 0) { + this.batchSize = batchSize; + } + return this; + } + + public WriteDoFn withTimeout(int timeout) { + if (timeout > 0) { + this.timeout = timeout; + } + return this; + } + + @Setup + public void setup() { + this.redisIngestionClient.setup(); + } + + @StartBundle + public void startBundle() { + try { + redisIngestionClient.connect(); + } catch (RedisException e) { + log.error("Connection to redis cannot be established ", e); + } + featureRows.clear(); + } + + private void executeBatch() throws Exception { + this.redisIngestionClient + .getBackOffExecutor() + .execute( + new Retriable() { + @Override + public void execute() throws ExecutionException, InterruptedException { + if (!redisIngestionClient.isConnected()) { + redisIngestionClient.connect(); + } + featureRows.forEach( + row -> { + redisIngestionClient.set(getKey(row), getValue(row)); + }); + redisIngestionClient.sync(); + } + + @Override + public Boolean isExceptionRetriable(Exception e) { + return e instanceof RedisException; + } + + @Override + public void cleanUpAfterFailure() {} + }); + } + + private FailedElement toFailedElement( + FeatureRow featureRow, Exception exception, String jobName) { + return FailedElement.newBuilder() + .setJobName(jobName) + .setTransformName("RedisClusterCustomIO") + .setPayload(featureRow.toString()) + .setErrorMessage(exception.getMessage()) + .setStackTrace(ExceptionUtils.getStackTrace(exception)) + .build(); + } + + private byte[] getKey(FeatureRow featureRow) { + FeatureSetSpec featureSetSpec = featureSetSpecs.get(featureRow.getFeatureSet()); + List entityNames = + featureSetSpec.getEntitiesList().stream() + .map(EntitySpec::getName) + .sorted() + .collect(Collectors.toList()); + + Map entityFields = new HashMap<>(); + Builder redisKeyBuilder = RedisKey.newBuilder().setFeatureSet(featureRow.getFeatureSet()); + for (Field field : featureRow.getFieldsList()) { + if (entityNames.contains(field.getName())) { + entityFields.putIfAbsent( + field.getName(), + Field.newBuilder().setName(field.getName()).setValue(field.getValue()).build()); + } + } + for (String entityName : entityNames) { + redisKeyBuilder.addEntities(entityFields.get(entityName)); + } + return redisKeyBuilder.build().toByteArray(); + } + + private byte[] getValue(FeatureRow featureRow) { + FeatureSetSpec spec = featureSetSpecs.get(featureRow.getFeatureSet()); + + List featureNames = + spec.getFeaturesList().stream().map(FeatureSpec::getName).collect(Collectors.toList()); + Map fieldValueOnlyMap = + featureRow.getFieldsList().stream() + .filter(field -> featureNames.contains(field.getName())) + .distinct() + .collect( + Collectors.toMap( + Field::getName, + field -> Field.newBuilder().setValue(field.getValue()).build())); + + List values = + featureNames.stream() + .sorted() + .map( + featureName -> + fieldValueOnlyMap.getOrDefault( + featureName, + Field.newBuilder() + .setValue(ValueProto.Value.getDefaultInstance()) + .build())) + .collect(Collectors.toList()); + + return FeatureRow.newBuilder() + .setEventTimestamp(featureRow.getEventTimestamp()) + .addAllFields(values) + .build() + .toByteArray(); + } + + @ProcessElement + public void processElement(ProcessContext context) { + FeatureRow featureRow = context.element(); + featureRows.add(featureRow); + if (featureRows.size() >= batchSize) { + try { + executeBatch(); + featureRows.forEach(row -> context.output(successfulInsertsTag, row)); + featureRows.clear(); + } catch (Exception e) { + featureRows.forEach( + failedMutation -> { + FailedElement failedElement = + toFailedElement(failedMutation, e, context.getPipelineOptions().getJobName()); + context.output(failedInsertsTupleTag, failedElement); + }); + featureRows.clear(); + } + } + } + + @FinishBundle + public void finishBundle(FinishBundleContext context) + throws IOException, InterruptedException { + if (featureRows.size() > 0) { + try { + executeBatch(); + featureRows.forEach( + row -> + context.output( + successfulInsertsTag, row, Instant.now(), GlobalWindow.INSTANCE)); + featureRows.clear(); + } catch (Exception e) { + featureRows.forEach( + failedMutation -> { + FailedElement failedElement = + toFailedElement(failedMutation, e, context.getPipelineOptions().getJobName()); + context.output( + failedInsertsTupleTag, failedElement, Instant.now(), GlobalWindow.INSTANCE); + }); + featureRows.clear(); + } + } + } + + @Teardown + public void teardown() { + redisIngestionClient.shutdown(); + } + } + } +} diff --git a/storage/connectors/rediscluster/src/main/java/feast/storage/connectors/rediscluster/writer/RedisClusterFeatureSink.java b/storage/connectors/rediscluster/src/main/java/feast/storage/connectors/rediscluster/writer/RedisClusterFeatureSink.java new file mode 100644 index 00000000000..160df6553cc --- /dev/null +++ b/storage/connectors/rediscluster/src/main/java/feast/storage/connectors/rediscluster/writer/RedisClusterFeatureSink.java @@ -0,0 +1,75 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.storage.connectors.rediscluster.writer; + +import com.google.auto.value.AutoValue; +import feast.proto.core.FeatureSetProto; +import feast.proto.core.StoreProto.Store.RedisClusterConfig; +import feast.proto.types.FeatureRowProto; +import feast.storage.api.writer.FeatureSink; +import feast.storage.api.writer.WriteResult; +import java.util.Map; +import org.apache.beam.sdk.transforms.PTransform; +import org.apache.beam.sdk.values.PCollection; + +@AutoValue +public abstract class RedisClusterFeatureSink implements FeatureSink { + + /** + * Initialize a {@link RedisClusterFeatureSink.Builder} from a {@link RedisClusterConfig}. + * + * @param redisClusterConfig {@link RedisClusterConfig} + * @param featureSetSpecs + * @return {@link RedisClusterFeatureSink.Builder} + */ + public static FeatureSink fromConfig( + RedisClusterConfig redisClusterConfig, + Map featureSetSpecs) { + return builder() + .setFeatureSetSpecs(featureSetSpecs) + .setRedisClusterConfig(redisClusterConfig) + .build(); + } + + public abstract RedisClusterConfig getRedisClusterConfig(); + + public abstract Map getFeatureSetSpecs(); + + public abstract Builder toBuilder(); + + public static Builder builder() { + return new AutoValue_RedisClusterFeatureSink.Builder(); + } + + @AutoValue.Builder + public abstract static class Builder { + public abstract Builder setRedisClusterConfig(RedisClusterConfig redisClusterConfig); + + public abstract Builder setFeatureSetSpecs( + Map featureSetSpecs); + + public abstract RedisClusterFeatureSink build(); + } + + @Override + public void prepareWrite(FeatureSetProto.FeatureSet featureSet) {} + + @Override + public PTransform, WriteResult> writer() { + return new RedisClusterCustomIO.Write(getRedisClusterConfig(), getFeatureSetSpecs()); + } +} diff --git a/storage/connectors/rediscluster/src/main/java/feast/storage/connectors/rediscluster/writer/RedisClusterIngestionClient.java b/storage/connectors/rediscluster/src/main/java/feast/storage/connectors/rediscluster/writer/RedisClusterIngestionClient.java new file mode 100644 index 00000000000..5bd70d08b43 --- /dev/null +++ b/storage/connectors/rediscluster/src/main/java/feast/storage/connectors/rediscluster/writer/RedisClusterIngestionClient.java @@ -0,0 +1,132 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.storage.connectors.rediscluster.writer; + +import com.google.common.collect.Lists; +import feast.proto.core.StoreProto; +import feast.storage.common.retry.BackOffExecutor; +import io.lettuce.core.LettuceFutures; +import io.lettuce.core.RedisFuture; +import io.lettuce.core.RedisURI; +import io.lettuce.core.cluster.RedisClusterClient; +import io.lettuce.core.cluster.api.StatefulRedisClusterConnection; +import io.lettuce.core.cluster.api.async.RedisAdvancedClusterAsyncCommands; +import io.lettuce.core.codec.ByteArrayCodec; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import org.joda.time.Duration; + +public class RedisClusterIngestionClient implements RedisIngestionClient { + + private final BackOffExecutor backOffExecutor; + private final List uriList; + private transient RedisClusterClient clusterClient; + private StatefulRedisClusterConnection connection; + private RedisAdvancedClusterAsyncCommands commands; + private List futures = Lists.newArrayList(); + + public RedisClusterIngestionClient(StoreProto.Store.RedisClusterConfig redisClusterConfig) { + this.uriList = + Arrays.stream(redisClusterConfig.getConnectionString().split(",")) + .map( + hostPort -> { + String[] hostPortSplit = hostPort.trim().split(":"); + return RedisURI.create(hostPortSplit[0], Integer.parseInt(hostPortSplit[1])); + }) + .collect(Collectors.toList()); + + long backoffMs = + redisClusterConfig.getInitialBackoffMs() > 0 ? redisClusterConfig.getInitialBackoffMs() : 1; + this.backOffExecutor = + new BackOffExecutor(redisClusterConfig.getMaxRetries(), Duration.millis(backoffMs)); + this.clusterClient = RedisClusterClient.create(uriList); + } + + @Override + public void setup() { + this.clusterClient = RedisClusterClient.create(this.uriList); + } + + @Override + public BackOffExecutor getBackOffExecutor() { + return this.backOffExecutor; + } + + @Override + public void shutdown() { + this.clusterClient.shutdown(); + } + + @Override + public void connect() { + if (!isConnected()) { + this.connection = clusterClient.connect(new ByteArrayCodec()); + this.commands = connection.async(); + } + } + + @Override + public boolean isConnected() { + return this.connection != null; + } + + @Override + public void sync() { + try { + LettuceFutures.awaitAll(60, TimeUnit.SECONDS, futures.toArray(new RedisFuture[0])); + } finally { + futures.clear(); + } + } + + @Override + public void pexpire(byte[] key, Long expiryMillis) { + futures.add(commands.pexpire(key, expiryMillis)); + } + + @Override + public void append(byte[] key, byte[] value) { + futures.add(commands.append(key, value)); + } + + @Override + public void set(byte[] key, byte[] value) { + futures.add(commands.set(key, value)); + } + + @Override + public void lpush(byte[] key, byte[] value) { + futures.add(commands.lpush(key, value)); + } + + @Override + public void rpush(byte[] key, byte[] value) { + futures.add(commands.rpush(key, value)); + } + + @Override + public void sadd(byte[] key, byte[] value) { + futures.add(commands.sadd(key, value)); + } + + @Override + public void zadd(byte[] key, Long score, byte[] value) { + futures.add(commands.zadd(key, score, value)); + } +} diff --git a/storage/connectors/rediscluster/src/main/java/feast/storage/connectors/rediscluster/writer/RedisIngestionClient.java b/storage/connectors/rediscluster/src/main/java/feast/storage/connectors/rediscluster/writer/RedisIngestionClient.java new file mode 100644 index 00000000000..5a0b54e6970 --- /dev/null +++ b/storage/connectors/rediscluster/src/main/java/feast/storage/connectors/rediscluster/writer/RedisIngestionClient.java @@ -0,0 +1,49 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.storage.connectors.rediscluster.writer; + +import feast.storage.common.retry.BackOffExecutor; +import java.io.Serializable; + +public interface RedisIngestionClient extends Serializable { + + void setup(); + + BackOffExecutor getBackOffExecutor(); + + void shutdown(); + + void connect(); + + boolean isConnected(); + + void sync(); + + void pexpire(byte[] key, Long expiryMillis); + + void append(byte[] key, byte[] value); + + void set(byte[] key, byte[] value); + + void lpush(byte[] key, byte[] value); + + void rpush(byte[] key, byte[] value); + + void sadd(byte[] key, byte[] value); + + void zadd(byte[] key, Long score, byte[] value); +} diff --git a/storage/connectors/rediscluster/src/test/java/feast/storage/connectors/rediscluster/retriever/RedisClusterOnlineRetrieverTest.java b/storage/connectors/rediscluster/src/test/java/feast/storage/connectors/rediscluster/retriever/RedisClusterOnlineRetrieverTest.java new file mode 100644 index 00000000000..16ab7962d60 --- /dev/null +++ b/storage/connectors/rediscluster/src/test/java/feast/storage/connectors/rediscluster/retriever/RedisClusterOnlineRetrieverTest.java @@ -0,0 +1,246 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2020 The Feast Authors + * + * 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 feast.storage.connectors.rediscluster.retriever; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.protobuf.AbstractMessageLite; +import com.google.protobuf.Duration; +import com.google.protobuf.Timestamp; +import feast.proto.core.FeatureSetProto.EntitySpec; +import feast.proto.core.FeatureSetProto.FeatureSetSpec; +import feast.proto.core.FeatureSetProto.FeatureSpec; +import feast.proto.serving.ServingAPIProto.FeatureReference; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequest.EntityRow; +import feast.proto.storage.RedisProto.RedisKey; +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.proto.types.FieldProto.Field; +import feast.proto.types.ValueProto.Value; +import feast.storage.api.retriever.FeatureSetRequest; +import feast.storage.api.retriever.OnlineRetriever; +import io.lettuce.core.KeyValue; +import io.lettuce.core.cluster.api.StatefulRedisClusterConnection; +import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; + +public class RedisClusterOnlineRetrieverTest { + + @Mock StatefulRedisClusterConnection connection; + + @Mock RedisAdvancedClusterCommands syncCommands; + + private OnlineRetriever redisClusterOnlineRetriever; + private byte[][] redisKeyList; + + @Before + public void setUp() { + initMocks(this); + when(connection.sync()).thenReturn(syncCommands); + redisClusterOnlineRetriever = RedisClusterOnlineRetriever.create(connection); + redisKeyList = + Lists.newArrayList( + RedisKey.newBuilder() + .setFeatureSet("project/featureSet") + .addAllEntities( + Lists.newArrayList( + Field.newBuilder().setName("entity1").setValue(intValue(1)).build(), + Field.newBuilder().setName("entity2").setValue(strValue("a")).build())) + .build(), + RedisKey.newBuilder() + .setFeatureSet("project/featureSet") + .addAllEntities( + Lists.newArrayList( + Field.newBuilder().setName("entity1").setValue(intValue(2)).build(), + Field.newBuilder().setName("entity2").setValue(strValue("b")).build())) + .build()) + .stream() + .map(AbstractMessageLite::toByteArray) + .collect(Collectors.toList()) + .toArray(new byte[0][0]); + } + + @Test + public void shouldReturnResponseWithValuesIfKeysPresent() { + FeatureSetRequest featureSetRequest = + FeatureSetRequest.newBuilder() + .setSpec(getFeatureSetSpec()) + .addFeatureReference( + FeatureReference.newBuilder().setName("feature1").setProject("project").build()) + .addFeatureReference( + FeatureReference.newBuilder().setName("feature2").setProject("project").build()) + .build(); + List entityRows = + ImmutableList.of( + EntityRow.newBuilder() + .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) + .putFields("entity1", intValue(1)) + .putFields("entity2", strValue("a")) + .build(), + EntityRow.newBuilder() + .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) + .putFields("entity1", intValue(2)) + .putFields("entity2", strValue("b")) + .build()); + + List featureRows = + Lists.newArrayList( + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setSeconds(100)) + .addAllFields( + Lists.newArrayList( + Field.newBuilder().setValue(intValue(1)).build(), + Field.newBuilder().setValue(intValue(1)).build())) + .build(), + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setSeconds(100)) + .addAllFields( + Lists.newArrayList( + Field.newBuilder().setValue(intValue(2)).build(), + Field.newBuilder().setValue(intValue(2)).build())) + .build()); + + List> featureRowBytes = + featureRows.stream() + .map(x -> KeyValue.from(new byte[1], Optional.of(x.toByteArray()))) + .collect(Collectors.toList()); + + redisClusterOnlineRetriever = RedisClusterOnlineRetriever.create(connection); + when(connection.sync()).thenReturn(syncCommands); + when(syncCommands.mget(redisKeyList)).thenReturn(featureRowBytes); + + List> expected = + List.of( + Lists.newArrayList( + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setSeconds(100)) + .setFeatureSet("project/featureSet") + .addAllFields( + Lists.newArrayList( + Field.newBuilder().setName("feature1").setValue(intValue(1)).build(), + Field.newBuilder().setName("feature2").setValue(intValue(1)).build())) + .build(), + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setSeconds(100)) + .setFeatureSet("project/featureSet") + .addAllFields( + Lists.newArrayList( + Field.newBuilder().setName("feature1").setValue(intValue(2)).build(), + Field.newBuilder().setName("feature2").setValue(intValue(2)).build())) + .build())); + + List> actual = + redisClusterOnlineRetriever.getOnlineFeatures(entityRows, List.of(featureSetRequest)); + assertThat(actual, equalTo(expected)); + } + + @Test + public void shouldReturnResponseWithUnsetValuesIfKeysNotPresent() { + FeatureSetRequest featureSetRequest = + FeatureSetRequest.newBuilder() + .setSpec(getFeatureSetSpec()) + .addFeatureReference( + FeatureReference.newBuilder().setName("feature1").setProject("project").build()) + .addFeatureReference( + FeatureReference.newBuilder().setName("feature2").setProject("project").build()) + .build(); + List entityRows = + ImmutableList.of( + EntityRow.newBuilder() + .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) + .putFields("entity1", intValue(1)) + .putFields("entity2", strValue("a")) + .build(), + EntityRow.newBuilder() + .setEntityTimestamp(Timestamp.newBuilder().setSeconds(100)) + .putFields("entity1", intValue(2)) + .putFields("entity2", strValue("b")) + .build()); + + List featureRows = + Lists.newArrayList( + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setSeconds(100)) + .addAllFields( + Lists.newArrayList( + Field.newBuilder().setValue(intValue(1)).build(), + Field.newBuilder().setValue(intValue(1)).build())) + .build()); + + List> featureRowBytes = + featureRows.stream() + .map(x -> KeyValue.from(new byte[1], Optional.of(x.toByteArray()))) + .collect(Collectors.toList()); + featureRowBytes.add(null); + + redisClusterOnlineRetriever = RedisClusterOnlineRetriever.create(connection); + when(connection.sync()).thenReturn(syncCommands); + when(syncCommands.mget(redisKeyList)).thenReturn(featureRowBytes); + + List> expected = + List.of( + Lists.newArrayList( + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setSeconds(100)) + .setFeatureSet("project/featureSet") + .addAllFields( + Lists.newArrayList( + Field.newBuilder().setName("feature1").setValue(intValue(1)).build(), + Field.newBuilder().setName("feature2").setValue(intValue(1)).build())) + .build(), + FeatureRow.newBuilder() + .setFeatureSet("project/featureSet") + .addAllFields( + Lists.newArrayList( + Field.newBuilder().setName("feature1").build(), + Field.newBuilder().setName("feature2").build())) + .build())); + + List> actual = + redisClusterOnlineRetriever.getOnlineFeatures(entityRows, List.of(featureSetRequest)); + assertThat(actual, equalTo(expected)); + } + + private Value intValue(int val) { + return Value.newBuilder().setInt64Val(val).build(); + } + + private Value strValue(String val) { + return Value.newBuilder().setStringVal(val).build(); + } + + private FeatureSetSpec getFeatureSetSpec() { + return FeatureSetSpec.newBuilder() + .setProject("project") + .setName("featureSet") + .addEntities(EntitySpec.newBuilder().setName("entity1")) + .addEntities(EntitySpec.newBuilder().setName("entity2")) + .addFeatures(FeatureSpec.newBuilder().setName("feature1")) + .addFeatures(FeatureSpec.newBuilder().setName("feature2")) + .setMaxAge(Duration.newBuilder().setSeconds(30)) // default + .build(); + } +} diff --git a/storage/connectors/rediscluster/src/test/java/feast/storage/connectors/rediscluster/writer/RedisClusterFeatureSinkTest.java b/storage/connectors/rediscluster/src/test/java/feast/storage/connectors/rediscluster/writer/RedisClusterFeatureSinkTest.java new file mode 100644 index 00000000000..8c5afd3d679 --- /dev/null +++ b/storage/connectors/rediscluster/src/test/java/feast/storage/connectors/rediscluster/writer/RedisClusterFeatureSinkTest.java @@ -0,0 +1,503 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2019 The Feast Authors + * + * 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 feast.storage.connectors.rediscluster.writer; + +import static feast.storage.common.testing.TestUtil.field; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.protobuf.Timestamp; +import feast.proto.core.FeatureSetProto.EntitySpec; +import feast.proto.core.FeatureSetProto.FeatureSetSpec; +import feast.proto.core.FeatureSetProto.FeatureSpec; +import feast.proto.core.StoreProto.Store.RedisClusterConfig; +import feast.proto.storage.RedisProto.RedisKey; +import feast.proto.types.FeatureRowProto.FeatureRow; +import feast.proto.types.FieldProto.Field; +import feast.proto.types.ValueProto.Value; +import feast.proto.types.ValueProto.ValueType.Enum; +import io.lettuce.core.RedisURI; +import io.lettuce.core.cluster.RedisClusterClient; +import io.lettuce.core.cluster.api.StatefulRedisClusterConnection; +import io.lettuce.core.cluster.api.sync.RedisClusterCommands; +import io.lettuce.core.codec.ByteArrayCodec; +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.*; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import net.ishiis.redis.unit.RedisCluster; +import org.apache.beam.sdk.testing.PAssert; +import org.apache.beam.sdk.testing.TestPipeline; +import org.apache.beam.sdk.transforms.Count; +import org.apache.beam.sdk.transforms.Create; +import org.apache.beam.sdk.values.PCollection; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +public class RedisClusterFeatureSinkTest { + @Rule public transient TestPipeline p = TestPipeline.create(); + + private static String REDIS_CLUSTER_HOST = "localhost"; + private static int REDIS_CLUSTER_PORT1 = 6380; + private static int REDIS_CLUSTER_PORT2 = 6381; + private static int REDIS_CLUSTER_PORT3 = 6382; + private static String CONNECTION_STRING = "localhost:6380,localhost:6381,localhost:6382"; + private RedisCluster redisCluster; + private RedisClusterClient redisClusterClient; + private RedisClusterCommands redisClusterCommands; + + private RedisClusterFeatureSink redisClusterFeatureSink; + + @Before + public void setUp() throws IOException { + redisCluster = new RedisCluster(REDIS_CLUSTER_PORT1, REDIS_CLUSTER_PORT2, REDIS_CLUSTER_PORT3); + redisCluster.start(); + redisClusterClient = + RedisClusterClient.create( + Arrays.asList( + RedisURI.create(REDIS_CLUSTER_HOST, REDIS_CLUSTER_PORT1), + RedisURI.create(REDIS_CLUSTER_HOST, REDIS_CLUSTER_PORT2), + RedisURI.create(REDIS_CLUSTER_HOST, REDIS_CLUSTER_PORT3))); + StatefulRedisClusterConnection connection = + redisClusterClient.connect(new ByteArrayCodec()); + redisClusterCommands = connection.sync(); + redisClusterCommands.setTimeout(java.time.Duration.ofMillis(600000)); + + FeatureSetSpec spec1 = + FeatureSetSpec.newBuilder() + .setName("fs") + .setProject("myproject") + .addEntities(EntitySpec.newBuilder().setName("entity").setValueType(Enum.INT64).build()) + .addFeatures( + FeatureSpec.newBuilder().setName("feature").setValueType(Enum.STRING).build()) + .build(); + + FeatureSetSpec spec2 = + FeatureSetSpec.newBuilder() + .setName("feature_set") + .setProject("myproject") + .addEntities( + EntitySpec.newBuilder() + .setName("entity_id_primary") + .setValueType(Enum.INT32) + .build()) + .addEntities( + EntitySpec.newBuilder() + .setName("entity_id_secondary") + .setValueType(Enum.STRING) + .build()) + .addFeatures( + FeatureSpec.newBuilder().setName("feature_1").setValueType(Enum.STRING).build()) + .addFeatures( + FeatureSpec.newBuilder().setName("feature_2").setValueType(Enum.INT64).build()) + .build(); + + Map specMap = + ImmutableMap.of("myproject/fs", spec1, "myproject/feature_set", spec2); + RedisClusterConfig redisClusterConfig = + RedisClusterConfig.newBuilder() + .setConnectionString(CONNECTION_STRING) + .setInitialBackoffMs(2000) + .setMaxRetries(4) + .build(); + + redisClusterFeatureSink = + RedisClusterFeatureSink.builder() + .setFeatureSetSpecs(specMap) + .setRedisClusterConfig(redisClusterConfig) + .build(); + } + + static boolean deleteDirectory(File directoryToBeDeleted) { + File[] allContents = directoryToBeDeleted.listFiles(); + if (allContents != null) { + for (File file : allContents) { + deleteDirectory(file); + } + } + return directoryToBeDeleted.delete(); + } + + @After + public void teardown() { + redisClusterClient.shutdown(); + redisCluster.stop(); + deleteDirectory(new File(String.valueOf(Paths.get(System.getProperty("user.dir"), ".redis")))); + } + + @Test + public void shouldWriteToRedis() { + + HashMap kvs = new LinkedHashMap<>(); + kvs.put( + RedisKey.newBuilder() + .setFeatureSet("myproject/fs") + .addEntities(field("entity", 1, Enum.INT64)) + .build(), + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.getDefaultInstance()) + .addFields(Field.newBuilder().setValue(Value.newBuilder().setStringVal("one"))) + .build()); + kvs.put( + RedisKey.newBuilder() + .setFeatureSet("myproject/fs") + .addEntities(field("entity", 2, Enum.INT64)) + .build(), + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.getDefaultInstance()) + .addFields(Field.newBuilder().setValue(Value.newBuilder().setStringVal("two"))) + .build()); + + List featureRows = + ImmutableList.of( + FeatureRow.newBuilder() + .setFeatureSet("myproject/fs") + .addFields(field("entity", 1, Enum.INT64)) + .addFields(field("feature", "one", Enum.STRING)) + .build(), + FeatureRow.newBuilder() + .setFeatureSet("myproject/fs") + .addFields(field("entity", 2, Enum.INT64)) + .addFields(field("feature", "two", Enum.STRING)) + .build()); + + p.apply(Create.of(featureRows)).apply(redisClusterFeatureSink.writer()); + p.run(); + + kvs.forEach( + (key, value) -> { + byte[] actual = redisClusterCommands.get(key.toByteArray()); + assertThat(actual, equalTo(value.toByteArray())); + }); + } + + @Test(timeout = 15000) + public void shouldRetryFailConnection() throws InterruptedException { + HashMap kvs = new LinkedHashMap<>(); + kvs.put( + RedisKey.newBuilder() + .setFeatureSet("myproject/fs") + .addEntities(field("entity", 1, Enum.INT64)) + .build(), + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.getDefaultInstance()) + .addFields(Field.newBuilder().setValue(Value.newBuilder().setStringVal("one"))) + .build()); + + List featureRows = + ImmutableList.of( + FeatureRow.newBuilder() + .setFeatureSet("myproject/fs") + .addFields(field("entity", 1, Enum.INT64)) + .addFields(field("feature", "one", Enum.STRING)) + .build()); + + PCollection failedElementCount = + p.apply(Create.of(featureRows)) + .apply(redisClusterFeatureSink.writer()) + .getFailedInserts() + .apply(Count.globally()); + + redisCluster.stop(); + final ScheduledThreadPoolExecutor redisRestartExecutor = new ScheduledThreadPoolExecutor(1); + ScheduledFuture scheduledRedisRestart = + redisRestartExecutor.schedule( + () -> { + redisCluster.start(); + }, + 3, + TimeUnit.SECONDS); + + PAssert.that(failedElementCount).containsInAnyOrder(0L); + p.run(); + scheduledRedisRestart.cancel(true); + + kvs.forEach( + (key, value) -> { + byte[] actual = redisClusterCommands.get(key.toByteArray()); + assertThat(actual, equalTo(value.toByteArray())); + }); + } + + @Test + public void shouldProduceFailedElementIfRetryExceeded() { + RedisClusterConfig redisClusterConfig = + RedisClusterConfig.newBuilder() + .setConnectionString(CONNECTION_STRING) + .setInitialBackoffMs(2000) + .setMaxRetries(1) + .build(); + + FeatureSetSpec spec1 = + FeatureSetSpec.newBuilder() + .setName("fs") + .setProject("myproject") + .addEntities(EntitySpec.newBuilder().setName("entity").setValueType(Enum.INT64).build()) + .addFeatures( + FeatureSpec.newBuilder().setName("feature").setValueType(Enum.STRING).build()) + .build(); + Map specMap = ImmutableMap.of("myproject/fs", spec1); + redisClusterFeatureSink = + RedisClusterFeatureSink.builder() + .setFeatureSetSpecs(specMap) + .setRedisClusterConfig(redisClusterConfig) + .build(); + redisCluster.stop(); + + List featureRows = + ImmutableList.of( + FeatureRow.newBuilder() + .setFeatureSet("myproject/fs") + .addFields(field("entity", 1, Enum.INT64)) + .addFields(field("feature", "one", Enum.STRING)) + .build()); + + PCollection failedElementCount = + p.apply(Create.of(featureRows)) + .apply(redisClusterFeatureSink.writer()) + .getFailedInserts() + .apply(Count.globally()); + + PAssert.that(failedElementCount).containsInAnyOrder(1L); + p.run(); + } + + @Test + public void shouldConvertRowWithDuplicateEntitiesToValidKey() { + + FeatureRow offendingRow = + FeatureRow.newBuilder() + .setFeatureSet("myproject/feature_set") + .setEventTimestamp(Timestamp.newBuilder().setSeconds(10)) + .addFields( + Field.newBuilder() + .setName("entity_id_primary") + .setValue(Value.newBuilder().setInt32Val(1))) + .addFields( + Field.newBuilder() + .setName("entity_id_primary") + .setValue(Value.newBuilder().setInt32Val(2))) + .addFields( + Field.newBuilder() + .setName("entity_id_secondary") + .setValue(Value.newBuilder().setStringVal("a"))) + .addFields( + Field.newBuilder() + .setName("feature_1") + .setValue(Value.newBuilder().setStringVal("strValue1"))) + .addFields( + Field.newBuilder() + .setName("feature_2") + .setValue(Value.newBuilder().setInt64Val(1001))) + .build(); + + RedisKey expectedKey = + RedisKey.newBuilder() + .setFeatureSet("myproject/feature_set") + .addEntities( + Field.newBuilder() + .setName("entity_id_primary") + .setValue(Value.newBuilder().setInt32Val(1))) + .addEntities( + Field.newBuilder() + .setName("entity_id_secondary") + .setValue(Value.newBuilder().setStringVal("a"))) + .build(); + + FeatureRow expectedValue = + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setSeconds(10)) + .addFields(Field.newBuilder().setValue(Value.newBuilder().setStringVal("strValue1"))) + .addFields(Field.newBuilder().setValue(Value.newBuilder().setInt64Val(1001))) + .build(); + + p.apply(Create.of(offendingRow)).apply(redisClusterFeatureSink.writer()); + + p.run(); + + byte[] actual = redisClusterCommands.get(expectedKey.toByteArray()); + assertThat(actual, equalTo(expectedValue.toByteArray())); + } + + @Test + public void shouldConvertRowWithOutOfOrderFieldsToValidKey() { + FeatureRow offendingRow = + FeatureRow.newBuilder() + .setFeatureSet("myproject/feature_set") + .setEventTimestamp(Timestamp.newBuilder().setSeconds(10)) + .addFields( + Field.newBuilder() + .setName("entity_id_secondary") + .setValue(Value.newBuilder().setStringVal("a"))) + .addFields( + Field.newBuilder() + .setName("entity_id_primary") + .setValue(Value.newBuilder().setInt32Val(1))) + .addFields( + Field.newBuilder() + .setName("feature_2") + .setValue(Value.newBuilder().setInt64Val(1001))) + .addFields( + Field.newBuilder() + .setName("feature_1") + .setValue(Value.newBuilder().setStringVal("strValue1"))) + .build(); + + RedisKey expectedKey = + RedisKey.newBuilder() + .setFeatureSet("myproject/feature_set") + .addEntities( + Field.newBuilder() + .setName("entity_id_primary") + .setValue(Value.newBuilder().setInt32Val(1))) + .addEntities( + Field.newBuilder() + .setName("entity_id_secondary") + .setValue(Value.newBuilder().setStringVal("a"))) + .build(); + + List expectedFields = + Arrays.asList( + Field.newBuilder().setValue(Value.newBuilder().setStringVal("strValue1")).build(), + Field.newBuilder().setValue(Value.newBuilder().setInt64Val(1001)).build()); + FeatureRow expectedValue = + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setSeconds(10)) + .addAllFields(expectedFields) + .build(); + + p.apply(Create.of(offendingRow)).apply(redisClusterFeatureSink.writer()); + + p.run(); + + byte[] actual = redisClusterCommands.get(expectedKey.toByteArray()); + assertThat(actual, equalTo(expectedValue.toByteArray())); + } + + @Test + public void shouldMergeDuplicateFeatureFields() { + FeatureRow featureRowWithDuplicatedFeatureFields = + FeatureRow.newBuilder() + .setFeatureSet("myproject/feature_set") + .setEventTimestamp(Timestamp.newBuilder().setSeconds(10)) + .addFields( + Field.newBuilder() + .setName("entity_id_primary") + .setValue(Value.newBuilder().setInt32Val(1))) + .addFields( + Field.newBuilder() + .setName("entity_id_secondary") + .setValue(Value.newBuilder().setStringVal("a"))) + .addFields( + Field.newBuilder() + .setName("feature_1") + .setValue(Value.newBuilder().setStringVal("strValue1"))) + .addFields( + Field.newBuilder() + .setName("feature_1") + .setValue(Value.newBuilder().setStringVal("strValue1"))) + .addFields( + Field.newBuilder() + .setName("feature_2") + .setValue(Value.newBuilder().setInt64Val(1001))) + .build(); + + RedisKey expectedKey = + RedisKey.newBuilder() + .setFeatureSet("myproject/feature_set") + .addEntities( + Field.newBuilder() + .setName("entity_id_primary") + .setValue(Value.newBuilder().setInt32Val(1))) + .addEntities( + Field.newBuilder() + .setName("entity_id_secondary") + .setValue(Value.newBuilder().setStringVal("a"))) + .build(); + + FeatureRow expectedValue = + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setSeconds(10)) + .addFields(Field.newBuilder().setValue(Value.newBuilder().setStringVal("strValue1"))) + .addFields(Field.newBuilder().setValue(Value.newBuilder().setInt64Val(1001))) + .build(); + + p.apply(Create.of(featureRowWithDuplicatedFeatureFields)) + .apply(redisClusterFeatureSink.writer()); + + p.run(); + + byte[] actual = redisClusterCommands.get(expectedKey.toByteArray()); + assertThat(actual, equalTo(expectedValue.toByteArray())); + } + + @Test + public void shouldPopulateMissingFeatureValuesWithDefaultInstance() { + FeatureRow featureRowWithDuplicatedFeatureFields = + FeatureRow.newBuilder() + .setFeatureSet("myproject/feature_set") + .setEventTimestamp(Timestamp.newBuilder().setSeconds(10)) + .addFields( + Field.newBuilder() + .setName("entity_id_primary") + .setValue(Value.newBuilder().setInt32Val(1))) + .addFields( + Field.newBuilder() + .setName("entity_id_secondary") + .setValue(Value.newBuilder().setStringVal("a"))) + .addFields( + Field.newBuilder() + .setName("feature_1") + .setValue(Value.newBuilder().setStringVal("strValue1"))) + .build(); + + RedisKey expectedKey = + RedisKey.newBuilder() + .setFeatureSet("myproject/feature_set") + .addEntities( + Field.newBuilder() + .setName("entity_id_primary") + .setValue(Value.newBuilder().setInt32Val(1))) + .addEntities( + Field.newBuilder() + .setName("entity_id_secondary") + .setValue(Value.newBuilder().setStringVal("a"))) + .build(); + + FeatureRow expectedValue = + FeatureRow.newBuilder() + .setEventTimestamp(Timestamp.newBuilder().setSeconds(10)) + .addFields(Field.newBuilder().setValue(Value.newBuilder().setStringVal("strValue1"))) + .addFields(Field.newBuilder().setValue(Value.getDefaultInstance())) + .build(); + + p.apply(Create.of(featureRowWithDuplicatedFeatureFields)) + .apply(redisClusterFeatureSink.writer()); + + p.run(); + + byte[] actual = redisClusterCommands.get(expectedKey.toByteArray()); + assertThat(actual, equalTo(expectedValue.toByteArray())); + } +} diff --git a/tests/e2e/all_types_parquet/all_types_parquet.yaml b/tests/e2e/all_types_parquet/all_types_parquet.yaml index 2043b6b473d..fa95ce13be0 100644 --- a/tests/e2e/all_types_parquet/all_types_parquet.yaml +++ b/tests/e2e/all_types_parquet/all_types_parquet.yaml @@ -6,7 +6,7 @@ spec: valueType: INT64 features: - name: int32_feature_parquet - valueType: INT64 + valueType: INT32 - name: int64_feature_parquet valueType: INT64 - name: float_feature_parquet diff --git a/tests/e2e/basic-ingest-redis-serving.py b/tests/e2e/basic-ingest-redis-serving.py index 1aeccfa5a3a..311f1282477 100644 --- a/tests/e2e/basic-ingest-redis-serving.py +++ b/tests/e2e/basic-ingest-redis-serving.py @@ -2,15 +2,20 @@ import math import random import time +import grpc from feast.entity import Entity from feast.serving.ServingService_pb2 import ( GetOnlineFeaturesRequest, GetOnlineFeaturesResponse, ) +from feast.core.IngestionJob_pb2 import IngestionJobStatus +from feast.core.CoreService_pb2_grpc import CoreServiceStub +from feast.core import CoreService_pb2 from feast.types.Value_pb2 import Value as Value from feast.client import Client -from feast.feature_set import FeatureSet +from feast.feature_set import FeatureSet, FeatureSetRef from feast.type_map import ValueType +from feast.constants import FEAST_DEFAULT_OPTIONS, CONFIG_PROJECT_KEY from google.protobuf.duration_pb2 import Duration from datetime import datetime import pytz @@ -46,7 +51,6 @@ def client(core_url, serving_url, allow_dirty): # Get client for core and serving client = Client(core_url=core_url, serving_url=serving_url) client.create_project(PROJECT_NAME) - client.set_project(PROJECT_NAME) # Ensure Feast core is active, but empty if not allow_dirty: @@ -58,80 +62,94 @@ def client(core_url, serving_url, allow_dirty): return client - -@pytest.fixture(scope='module') -def basic_dataframe(): +def basic_dataframe(entities, features, ingest_time, n_size): offset = random.randint(1000, 100000) # ensure a unique key space is used - return pd.DataFrame( - { - "datetime": [datetime.utcnow().replace(tzinfo=pytz.utc) for _ in - range(5)], - "customer_id": [offset + inc for inc in range(5)], - "daily_transactions": [np.random.rand() for _ in range(5)], - "total_transactions": [512 for _ in range(5)], - } - ) - + df_dict = { + "datetime": [ingest_time.replace(tzinfo=pytz.utc) for _ in + range(n_size)], + } + for entity_name in entities: + df_dict[entity_name] = list(range(1, n_size + 1)) + for feature_name in features: + df_dict[feature_name] = [np.random.rand() for _ in range(n_size)] + return pd.DataFrame(df_dict) + + +@pytest.fixture(scope="module") +def ingest_time(): + return datetime.utcnow() + +@pytest.fixture(scope="module") +def cust_trans_df(ingest_time): + return basic_dataframe(entities=["customer_id"], + features=["daily_transactions", "total_transactions"], + ingest_time=ingest_time, + n_size=5) + +@pytest.fixture(scope="module") +def driver_df(ingest_time): + return basic_dataframe(entities=["driver_id"], + features=["rating", "cost"], + ingest_time=ingest_time, + n_size=5) @pytest.mark.timeout(45) @pytest.mark.run(order=10) def test_basic_register_feature_set_success(client): - # Load feature set from file + # Register feature set without project cust_trans_fs_expected = FeatureSet.from_yaml("basic/cust_trans_fs.yaml") + driver_fs_expected = FeatureSet.from_yaml("basic/driver_fs.yaml") + client.apply(cust_trans_fs_expected) + client.apply(driver_fs_expected) + cust_trans_fs_actual = client.get_feature_set("customer_transactions") + assert cust_trans_fs_actual == cust_trans_fs_expected + driver_fs_actual = client.get_feature_set("driver") + assert driver_fs_actual == driver_fs_expected + # Register feature set with project + cust_trans_fs_expected = FeatureSet.from_yaml("basic/cust_trans_fs.yaml") client.set_project(PROJECT_NAME) - - # Register feature set client.apply(cust_trans_fs_expected) - - cust_trans_fs_actual = client.get_feature_set(name="customer_transactions") - + cust_trans_fs_actual = client.get_feature_set("customer_transactions", + project=PROJECT_NAME) assert cust_trans_fs_actual == cust_trans_fs_expected - if cust_trans_fs_actual is None: - raise Exception( - "Client cannot retrieve 'customer_transactions' FeatureSet " - "after registration. Either Feast Core does not save the " - "FeatureSet correctly or the client needs to wait longer for FeatureSet " - "to be committed." - ) - + # reset client's project for other tests + client.set_project() @pytest.mark.timeout(300) @pytest.mark.run(order=11) -def test_basic_ingest_success(client, basic_dataframe): - client.set_project(PROJECT_NAME) - +def test_basic_ingest_success(client, cust_trans_df, driver_df): cust_trans_fs = client.get_feature_set(name="customer_transactions") + driver_fs = client.get_feature_set(name="driver") # Ingest customer transaction data - client.ingest(cust_trans_fs, basic_dataframe) + client.ingest(cust_trans_fs, cust_trans_df) + client.ingest(driver_fs, driver_df) time.sleep(5) @pytest.mark.timeout(45) @pytest.mark.run(order=12) -def test_basic_retrieve_online_success(client, basic_dataframe): +def test_basic_retrieve_online_success(client, cust_trans_df): # Poll serving for feature values until the correct values are returned while True: time.sleep(1) - - client.set_project(PROJECT_NAME) - response = client.get_online_features( entity_rows=[ GetOnlineFeaturesRequest.EntityRow( fields={ "customer_id": Value( - int64_val=basic_dataframe.iloc[0]["customer_id"] + int64_val=cust_trans_df.iloc[0]["customer_id"] ) } ) ], + # Test retrieve with different variations of the string feature refs feature_refs=[ "daily_transactions", "total_transactions", - ], + ] ) # type: GetOnlineFeaturesResponse if response is None: @@ -139,11 +157,11 @@ def test_basic_retrieve_online_success(client, basic_dataframe): returned_daily_transactions = float( response.field_values[0] - .fields[PROJECT_NAME + "/daily_transactions"] - .float_val + .fields["daily_transactions"] + .float_val ) sent_daily_transactions = float( - basic_dataframe.iloc[0]["daily_transactions"]) + cust_trans_df.iloc[0]["daily_transactions"]) if math.isclose( sent_daily_transactions, @@ -153,6 +171,84 @@ def test_basic_retrieve_online_success(client, basic_dataframe): break +@pytest.mark.timeout(45) +@pytest.mark.run(order=13) +def test_basic_retrieve_online_multiple_featureset(client, cust_trans_df, driver_df): + # Poll serving for feature values until the correct values are returned + while True: + time.sleep(1) + # Test retrieve with different variations of the string feature refs + # ie feature set inference for feature refs without specified feature set + feature_ref_df_mapping = [ + ("customer_transactions:daily_transactions", cust_trans_df), + ("driver:rating", driver_df), + ("total_transactions", cust_trans_df), + ] + response = client.get_online_features( + entity_rows=[ + GetOnlineFeaturesRequest.EntityRow( + fields={ + "customer_id": Value( + int64_val=cust_trans_df.iloc[0]["customer_id"] + ), + "driver_id": Value( + int64_val=driver_df.iloc[0]["driver_id"] + ) + } + ) + ], + feature_refs=[mapping[0] for mapping in feature_ref_df_mapping], + ) # type: GetOnlineFeaturesResponse + + if response is None: + continue + + def check_response(ingest_df, response, feature_ref): + returned_value = float( + response.field_values[0] + .fields[feature_ref] + .float_val + ) + feature_ref_splits = feature_ref.split(":") + if len(feature_ref_splits) == 1: + feature_name = feature_ref + else: + _, feature_name = feature_ref_splits + + sent_value = float( + ingest_df.iloc[0][feature_name]) + + return math.isclose( + sent_value, + returned_value, + abs_tol=FLOAT_TOLERANCE, + ) + if all([check_response(df, response, ref) for ref, df in feature_ref_df_mapping]): + break + +@pytest.mark.timeout(300) +@pytest.mark.run(order=19) +def test_basic_ingest_jobs(client): + # list ingestion jobs given featureset + cust_trans_fs = client.get_feature_set(name="customer_transactions") + ingest_jobs = client.list_ingest_jobs( + feature_set_ref=FeatureSetRef.from_feature_set(cust_trans_fs)) + # filter ingestion jobs to only those that are running + ingest_jobs = [job for job in ingest_jobs if job.status == IngestionJobStatus.RUNNING] + assert len(ingest_jobs) >= 1 + + for ingest_job in ingest_jobs: + # restart ingestion ingest_job + client.restart_ingest_job(ingest_job) + ingest_job.wait(IngestionJobStatus.RUNNING) + assert ingest_job.status == IngestionJobStatus.RUNNING + + # stop ingestion ingest_job + client.stop_ingest_job(ingest_job) + ingest_job.wait(IngestionJobStatus.ABORTED) + assert ingest_job.status == IngestionJobStatus.ABORTED + + @pytest.fixture(scope='module') def all_types_dataframe(): return pd.DataFrame( @@ -203,7 +299,7 @@ def all_types_dataframe(): # np.array([True, False, True]), # np.array([True, False, True]), # ], - # TODO: https://github.com/gojek/feast/issues/341 + # TODO: https://github.com/feast-dev/feast/issues/341 } ) @@ -297,21 +393,43 @@ def test_all_types_retrieve_online_success(client, all_types_dataframe): if response is None: continue - returned_float_list = ( response.field_values[0] - .fields[PROJECT_NAME+"/float_list_feature"] + .fields["float_list_feature"] .float_list_val.val ) sent_float_list = all_types_dataframe.iloc[0]["float_list_feature"] if math.isclose( - returned_float_list[0], sent_float_list[0], abs_tol=FLOAT_TOLERANCE + returned_float_list[0], sent_float_list[0], abs_tol=FLOAT_TOLERANCE ): break +@pytest.mark.timeout(300) +@pytest.mark.run(order=29) +def test_all_types_ingest_jobs(client, all_types_dataframe): + # list ingestion jobs given featureset + all_types_fs = client.get_feature_set(name="all_types") + ingest_jobs = client.list_ingest_jobs( + feature_set_ref=FeatureSetRef.from_feature_set(all_types_fs)) + # filter ingestion jobs to only those that are running + ingest_jobs = [job for job in ingest_jobs if job.status == IngestionJobStatus.RUNNING] + assert len(ingest_jobs) >= 1 + + for ingest_job in ingest_jobs: + # restart ingestion ingest_job + client.restart_ingest_job(ingest_job) + ingest_job.wait(IngestionJobStatus.RUNNING) + assert ingest_job.status == IngestionJobStatus.RUNNING + + # stop ingestion ingest_job + client.stop_ingest_job(ingest_job) + ingest_job.wait(IngestionJobStatus.ABORTED) + assert ingest_job.status == IngestionJobStatus.ABORTED + + @pytest.fixture(scope='module') def large_volume_dataframe(): ROW_COUNT = 100000 @@ -395,16 +513,16 @@ def test_large_volume_retrieve_online_success(client, large_volume_dataframe): returned_daily_transactions = float( response.field_values[0] - .fields[PROJECT_NAME + "/daily_transactions_large"] + .fields["daily_transactions_large"] .float_val ) sent_daily_transactions = float( large_volume_dataframe.iloc[0]["daily_transactions_large"]) if math.isclose( - sent_daily_transactions, - returned_daily_transactions, - abs_tol=FLOAT_TOLERANCE, + sent_daily_transactions, + returned_daily_transactions, + abs_tol=FLOAT_TOLERANCE, ): break @@ -419,14 +537,14 @@ def all_types_parquet_file(): "customer_id": [np.int32(random.randint(0, 10000)) for _ in range(COUNT)], "int32_feature_parquet": [np.int32(random.randint(0, 10000)) for _ in - range(COUNT)], + range(COUNT)], "int64_feature_parquet": [np.int64(random.randint(0, 10000)) for _ in - range(COUNT)], + range(COUNT)], "float_feature_parquet": [np.float(random.random()) for _ in range(COUNT)], "double_feature_parquet": [np.float64(random.random()) for _ in - range(COUNT)], + range(COUNT)], "string_feature_parquet": ["one" + str(random.random()) for _ in - range(COUNT)], + range(COUNT)], "bytes_feature_parquet": [b"one" for _ in range(COUNT)], "int32_list_feature_parquet": [ np.array([1, 2, 3, random.randint(0, 10000)], dtype=np.int32) @@ -460,7 +578,7 @@ def all_types_parquet_file(): ) # TODO: Boolean list is not being tested. - # https://github.com/gojek/feast/issues/341 + # https://github.com/feast-dev/feast/issues/341 file_path = os.path.join(tempfile.mkdtemp(), 'all_types.parquet') df.to_parquet(file_path, allow_truncated_timestamps=True) @@ -497,10 +615,84 @@ def test_all_types_parquet_register_feature_set_success(client): @pytest.mark.timeout(600) @pytest.mark.run(order=41) def test_all_types_infer_register_ingest_file_success(client, - all_types_parquet_file): + all_types_parquet_file): # Get feature set all_types_fs = client.get_feature_set(name="all_types_parquet") # Ingest user embedding data - client.ingest(feature_set=all_types_fs, source=all_types_parquet_file, - force_update=True) + client.ingest(feature_set=all_types_fs, source=all_types_parquet_file) + + +# TODO: rewrite these using python SDK once the labels are implemented there +class TestsBasedOnGrpc: + GRPC_CONNECTION_TIMEOUT = 3 + LABEL_KEY = "my" + LABEL_VALUE = "label" + + @pytest.fixture(scope="module") + def core_service_stub(self, core_url): + if core_url.endswith(":443"): + core_channel = grpc.secure_channel( + core_url, grpc.ssl_channel_credentials() + ) + else: + core_channel = grpc.insecure_channel(core_url) + + try: + grpc.channel_ready_future(core_channel).result(timeout=self.GRPC_CONNECTION_TIMEOUT) + except grpc.FutureTimeoutError: + raise ConnectionError( + f"Connection timed out while attempting to connect to Feast " + f"Core gRPC server {core_url} " + ) + core_service_stub = CoreServiceStub(core_channel) + return core_service_stub + + def apply_feature_set(self, core_service_stub, feature_set_proto): + try: + apply_fs_response = core_service_stub.ApplyFeatureSet( + CoreService_pb2.ApplyFeatureSetRequest(feature_set=feature_set_proto), + timeout=self.GRPC_CONNECTION_TIMEOUT, + ) # type: ApplyFeatureSetResponse + except grpc.RpcError as e: + raise grpc.RpcError(e.details()) + return apply_fs_response.feature_set + + def get_feature_set(self, core_service_stub, name, project): + try: + get_feature_set_response = core_service_stub.GetFeatureSet( + CoreService_pb2.GetFeatureSetRequest( + project=project, name=name.strip(), + ) + ) # type: GetFeatureSetResponse + except grpc.RpcError as e: + raise grpc.RpcError(e.details()) + return get_feature_set_response.feature_set + + @pytest.mark.timeout(45) + @pytest.mark.run(order=51) + def test_register_feature_set_with_labels(self, core_service_stub): + feature_set_name = "test_feature_set_labels" + feature_set_proto = FeatureSet(feature_set_name, PROJECT_NAME).to_proto() + feature_set_proto.spec.labels[self.LABEL_KEY] = self.LABEL_VALUE + self.apply_feature_set(core_service_stub, feature_set_proto) + + retrieved_feature_set = self.get_feature_set(core_service_stub, feature_set_name, PROJECT_NAME) + + assert self.LABEL_KEY in retrieved_feature_set.spec.labels + assert retrieved_feature_set.spec.labels[self.LABEL_KEY] == self.LABEL_VALUE + + @pytest.mark.timeout(45) + @pytest.mark.run(order=52) + def test_register_feature_with_labels(self, core_service_stub): + feature_set_name = "test_feature_labels" + feature_set_proto = FeatureSet(feature_set_name, PROJECT_NAME, features=[Feature("rating", ValueType.INT64)]) \ + .to_proto() + feature_set_proto.spec.features[0].labels[self.LABEL_KEY] = self.LABEL_VALUE + self.apply_feature_set(core_service_stub, feature_set_proto) + + retrieved_feature_set = self.get_feature_set(core_service_stub, feature_set_name, PROJECT_NAME) + retrieved_feature = retrieved_feature_set.spec.features[0] + + assert self.LABEL_KEY in retrieved_feature.labels + assert retrieved_feature.labels[self.LABEL_KEY] == self.LABEL_VALUE diff --git a/tests/e2e/basic/driver_fs.yaml b/tests/e2e/basic/driver_fs.yaml new file mode 100644 index 00000000000..f25ca956782 --- /dev/null +++ b/tests/e2e/basic/driver_fs.yaml @@ -0,0 +1,12 @@ +kind: feature_set +spec: + name: driver + entities: + - name: driver_id + valueType: INT64 + features: + - name: rating + valueType: FLOAT + - name: cost + valueType: FLOAT + maxAge: 3600s diff --git a/tests/e2e/bq-batch-retrieval.py b/tests/e2e/bq-batch-retrieval.py index 8616dd37a92..99b88a8dff7 100644 --- a/tests/e2e/bq-batch-retrieval.py +++ b/tests/e2e/bq-batch-retrieval.py @@ -1,27 +1,31 @@ +import math +import os import random import time +import uuid from datetime import datetime from datetime import timedelta from urllib.parse import urlparse -import uuid import numpy as np import pandas as pd import pytest import pytz from feast.client import Client +from feast.core.CoreService_pb2 import ListStoresRequest +from feast.core.IngestionJob_pb2 import IngestionJobStatus from feast.entity import Entity from feast.feature import Feature from feast.feature_set import FeatureSet from feast.type_map import ValueType -from google.cloud import storage +from google.cloud import storage, bigquery +from google.cloud.storage import Blob from google.protobuf.duration_pb2 import Duration from pandavro import to_avro -pd.set_option('display.max_columns', None) - -PROJECT_NAME = 'batch_' + uuid.uuid4().hex.upper()[0:6] +pd.set_option("display.max_columns", None) +PROJECT_NAME = "batch_" + uuid.uuid4().hex.upper()[0:6] @pytest.fixture(scope="module") def core_url(pytestconfig): @@ -48,66 +52,71 @@ def client(core_url, serving_url, allow_dirty): # Get client for core and serving client = Client(core_url=core_url, serving_url=serving_url) client.create_project(PROJECT_NAME) - client.set_project(PROJECT_NAME) # Ensure Feast core is active, but empty if not allow_dirty: feature_sets = client.list_feature_sets() if len(feature_sets) > 0: - raise Exception("Feast cannot have existing feature sets registered. Exiting tests.") + raise Exception( + "Feast cannot have existing feature sets registered. Exiting tests." + ) return client + @pytest.mark.first -def test_apply_all_featuresets(client): +@pytest.mark.direct_runner +@pytest.mark.dataflow_runner +@pytest.mark.run(order=1) +def test_batch_apply_all_featuresets(client): client.set_project(PROJECT_NAME) file_fs1 = FeatureSet( - "file_feature_set", - features=[Feature("feature_value1", ValueType.STRING)], - entities=[Entity("entity_id", ValueType.INT64)], - max_age=Duration(seconds=100), - ) + "file_feature_set", + features=[Feature("feature_value1", ValueType.STRING)], + entities=[Entity("entity_id", ValueType.INT64)], + max_age=Duration(seconds=100), + ) client.apply(file_fs1) gcs_fs1 = FeatureSet( - "gcs_feature_set", - features=[Feature("feature_value2", ValueType.STRING)], - entities=[Entity("entity_id", ValueType.INT64)], - max_age=Duration(seconds=100), - ) + "gcs_feature_set", + features=[Feature("feature_value2", ValueType.STRING)], + entities=[Entity("entity_id", ValueType.INT64)], + max_age=Duration(seconds=100), + ) client.apply(gcs_fs1) proc_time_fs = FeatureSet( - "processing_time", - features=[Feature("feature_value3", ValueType.STRING)], - entities=[Entity("entity_id", ValueType.INT64)], - max_age=Duration(seconds=100), - ) + "processing_time", + features=[Feature("feature_value3", ValueType.STRING)], + entities=[Entity("entity_id", ValueType.INT64)], + max_age=Duration(seconds=100), + ) client.apply(proc_time_fs) add_cols_fs = FeatureSet( - "additional_columns", - features=[Feature("feature_value4", ValueType.STRING)], - entities=[Entity("entity_id", ValueType.INT64)], - max_age=Duration(seconds=100), - ) + "additional_columns", + features=[Feature("feature_value4", ValueType.STRING)], + entities=[Entity("entity_id", ValueType.INT64)], + max_age=Duration(seconds=100), + ) client.apply(add_cols_fs) historical_fs = FeatureSet( - "historical", - features=[Feature("feature_value5", ValueType.STRING)], - entities=[Entity("entity_id", ValueType.INT64)], - max_age=Duration(seconds=100), - ) + "historical", + features=[Feature("feature_value5", ValueType.STRING)], + entities=[Entity("entity_id", ValueType.INT64)], + max_age=Duration(seconds=100), + ) client.apply(historical_fs) fs1 = FeatureSet( - "feature_set_1", - features=[Feature("feature_value6", ValueType.STRING)], - entities=[Entity("entity_id", ValueType.INT64)], - max_age=Duration(seconds=100), - ) + "feature_set_1", + features=[Feature("feature_value6", ValueType.STRING)], + entities=[Entity("entity_id", ValueType.INT64)], + max_age=Duration(seconds=100), + ) fs2 = FeatureSet( "feature_set_2", @@ -118,9 +127,20 @@ def test_apply_all_featuresets(client): client.apply(fs1) client.apply(fs2) + no_max_age_fs = FeatureSet( + "no_max_age", + features=[Feature("feature_value8", ValueType.INT64)], + entities=[Entity("entity_id", ValueType.INT64)], + max_age=Duration(seconds=0), + ) + client.apply(no_max_age_fs) + -def test_get_batch_features_with_file(client): - file_fs1 = client.get_feature_set(name="file_feature_set", version=1) +@pytest.mark.direct_runner +@pytest.mark.dataflow_runner +@pytest.mark.run(order=10) +def test_batch_get_batch_features_with_file(client): + file_fs1 = client.get_feature_set(name="file_feature_set") N_ROWS = 10 time_offset = datetime.utcnow().replace(tzinfo=pytz.utc) @@ -131,26 +151,38 @@ def test_get_batch_features_with_file(client): "feature_value1": [f"{i}" for i in range(N_ROWS)], } ) - client.ingest(file_fs1, features_1_df) + client.ingest(file_fs1, features_1_df, timeout=480) # Rename column (datetime -> event_timestamp) + features_1_df['datetime'] + pd.Timedelta(seconds=1) # adds buffer to avoid rounding errors features_1_df = features_1_df.rename(columns={"datetime": "event_timestamp"}) - to_avro(df=features_1_df[["event_timestamp", "entity_id"]], file_path_or_buffer="file_feature_set.avro") + to_avro( + df=features_1_df[["event_timestamp", "entity_id"]], + file_path_or_buffer="file_feature_set.avro", + ) time.sleep(15) feature_retrieval_job = client.get_batch_features( - entity_rows="file://file_feature_set.avro", feature_refs=[f"{PROJECT_NAME}/feature_value1:1"] + entity_rows="file://file_feature_set.avro", + feature_refs=["feature_value1"], + project=PROJECT_NAME, ) output = feature_retrieval_job.to_dataframe() + clean_up_remote_files(feature_retrieval_job.get_avro_files()) print(output.head()) - assert output["entity_id"].to_list() == [int(i) for i in output["feature_value1"].to_list()] + assert output["entity_id"].to_list() == [ + int(i) for i in output["feature_value1"].to_list() + ] -def test_get_batch_features_with_gs_path(client, gcs_path): - gcs_fs1 = client.get_feature_set(name="gcs_feature_set", version=1) +@pytest.mark.direct_runner +@pytest.mark.dataflow_runner +@pytest.mark.run(order=11) +def test_batch_get_batch_features_with_gs_path(client, gcs_path): + gcs_fs1 = client.get_feature_set(name="gcs_feature_set") N_ROWS = 10 time_offset = datetime.utcnow().replace(tzinfo=pytz.utc) @@ -161,14 +193,18 @@ def test_get_batch_features_with_gs_path(client, gcs_path): "feature_value2": [f"{i}" for i in range(N_ROWS)], } ) - client.ingest(gcs_fs1, features_1_df) + client.ingest(gcs_fs1, features_1_df, timeout=360) # Rename column (datetime -> event_timestamp) + features_1_df['datetime'] + pd.Timedelta(seconds=1) # adds buffer to avoid rounding errors features_1_df = features_1_df.rename(columns={"datetime": "event_timestamp"}) # Output file to local file_name = "gcs_feature_set.avro" - to_avro(df=features_1_df[["event_timestamp", "entity_id"]], file_path_or_buffer=file_name) + to_avro( + df=features_1_df[["event_timestamp", "entity_id"]], + file_path_or_buffer=file_name, + ) uri = urlparse(gcs_path) bucket = uri.hostname @@ -184,17 +220,24 @@ def test_get_batch_features_with_gs_path(client, gcs_path): time.sleep(15) feature_retrieval_job = client.get_batch_features( entity_rows=f"{gcs_path}{ts}/*", - feature_refs=[f"{PROJECT_NAME}/feature_value2:1"] + feature_refs=["feature_value2"], + project=PROJECT_NAME, ) output = feature_retrieval_job.to_dataframe() + clean_up_remote_files(feature_retrieval_job.get_avro_files()) + blob.delete() print(output.head()) - assert output["entity_id"].to_list() == [int(i) for i in output["feature_value2"].to_list()] + assert output["entity_id"].to_list() == [ + int(i) for i in output["feature_value2"].to_list() + ] -def test_order_by_creation_time(client): - proc_time_fs = client.get_feature_set(name="processing_time", version=1) +@pytest.mark.direct_runner +@pytest.mark.run(order=12) +def test_batch_order_by_creation_time(client): + proc_time_fs = client.get_feature_set(name="processing_time") time_offset = datetime.utcnow().replace(tzinfo=pytz.utc) N_ROWS = 10 @@ -216,21 +259,30 @@ def test_order_by_creation_time(client): time.sleep(15) client.ingest(proc_time_fs, correct_df) feature_retrieval_job = client.get_batch_features( - entity_rows=incorrect_df[["datetime", "entity_id"]], feature_refs=[f"{PROJECT_NAME}/feature_value3:1"] + entity_rows=incorrect_df[["datetime", "entity_id"]], + feature_refs=["feature_value3"], + project=PROJECT_NAME, ) output = feature_retrieval_job.to_dataframe() + clean_up_remote_files(feature_retrieval_job.get_avro_files()) print(output.head()) assert output["feature_value3"].to_list() == ["CORRECT"] * N_ROWS -def test_additional_columns_in_entity_table(client): - add_cols_fs = client.get_feature_set(name="additional_columns", version=1) +@pytest.mark.direct_runner +@pytest.mark.run(order=13) +def test_batch_additional_columns_in_entity_table(client): + add_cols_fs = client.get_feature_set(name="additional_columns") N_ROWS = 10 time_offset = datetime.utcnow().replace(tzinfo=pytz.utc) features_df = pd.DataFrame( - {"datetime": [time_offset] * N_ROWS, "entity_id": [i for i in range(N_ROWS)], "feature_value4": ["abc"] * N_ROWS} + { + "datetime": [time_offset] * N_ROWS, + "entity_id": [i for i in range(N_ROWS)], + "feature_value4": ["abc"] * N_ROWS, + } ) client.ingest(add_cols_fs, features_df) @@ -245,18 +297,28 @@ def test_additional_columns_in_entity_table(client): time.sleep(15) feature_retrieval_job = client.get_batch_features( - entity_rows=entity_df, feature_refs=[f"{PROJECT_NAME}/feature_value4:1"] + entity_rows=entity_df, + feature_refs=["feature_value4"], + project=PROJECT_NAME, ) output = feature_retrieval_job.to_dataframe().sort_values(by=["entity_id"]) + clean_up_remote_files(feature_retrieval_job.get_avro_files()) print(output.head(10)) - assert np.allclose(output["additional_float_col"], entity_df["additional_float_col"]) - assert output["additional_string_col"].to_list() == entity_df["additional_string_col"].to_list() + assert np.allclose( + output["additional_float_col"], entity_df["additional_float_col"] + ) + assert ( + output["additional_string_col"].to_list() + == entity_df["additional_string_col"].to_list() + ) assert output["feature_value4"].to_list() == features_df["feature_value4"].to_list() -def test_point_in_time_correctness_join(client): - historical_fs = client.get_feature_set(name="historical", version=1) +@pytest.mark.direct_runner +@pytest.mark.run(order=14) +def test_batch_point_in_time_correctness_join(client): + historical_fs = client.get_feature_set(name="historical") time_offset = datetime.utcnow().replace(tzinfo=pytz.utc) N_EXAMPLES = 10 @@ -273,22 +335,32 @@ def test_point_in_time_correctness_join(client): } ) entity_df = pd.DataFrame( - {"datetime": [time_offset - timedelta(seconds=10)] * N_EXAMPLES, "entity_id": [i for i in range(N_EXAMPLES)]} + { + "datetime": [time_offset - timedelta(seconds=10)] * N_EXAMPLES, + "entity_id": [i for i in range(N_EXAMPLES)], + } ) client.ingest(historical_fs, historical_df) time.sleep(15) - feature_retrieval_job = client.get_batch_features(entity_rows=entity_df, feature_refs=[f"{PROJECT_NAME}/feature_value5"]) + feature_retrieval_job = client.get_batch_features( + entity_rows=entity_df, + feature_refs=["feature_value5"], + project=PROJECT_NAME, + ) output = feature_retrieval_job.to_dataframe() + clean_up_remote_files(feature_retrieval_job.get_avro_files()) print(output.head()) assert output["feature_value5"].to_list() == ["CORRECT"] * N_EXAMPLES -def test_multiple_featureset_joins(client): - fs1 = client.get_feature_set(name="feature_set_1", version=1) - fs2 = client.get_feature_set(name="feature_set_2", version=1) +@pytest.mark.direct_runner +@pytest.mark.run(order=15) +def test_batch_multiple_featureset_joins(client): + fs1 = client.get_feature_set(name="feature_set_1") + fs2 = client.get_feature_set(name="feature_set_2") N_ROWS = 10 time_offset = datetime.utcnow().replace(tzinfo=pytz.utc) @@ -318,12 +390,273 @@ def test_multiple_featureset_joins(client): } ) + time.sleep(15) + # Test retrieve with different variations of the string feature refs + # ie feature set inference for feature refs without specified feature set + feature_retrieval_job = client.get_batch_features( + entity_rows=entity_df, + feature_refs=[ + "feature_value6", + "feature_set_2:other_feature_value7", + ], + project=PROJECT_NAME, + ) + output = feature_retrieval_job.to_dataframe() + clean_up_remote_files(feature_retrieval_job.get_avro_files()) + print(output.head()) + + assert output["entity_id"].to_list() == [ + int(i) for i in output["feature_value6"].to_list() + ] + assert ( + output["other_entity_id"].to_list() == output["feature_set_2__other_feature_value7"].to_list() + ) + + +@pytest.mark.direct_runner +@pytest.mark.run(order=16) +def test_batch_no_max_age(client): + no_max_age_fs = client.get_feature_set(name="no_max_age") + + time_offset = datetime.utcnow().replace(tzinfo=pytz.utc) + N_ROWS = 10 + features_8_df = pd.DataFrame( + { + "datetime": [time_offset] * N_ROWS, + "entity_id": [i for i in range(N_ROWS)], + "feature_value8": [i for i in range(N_ROWS)], + } + ) + client.ingest(no_max_age_fs, features_8_df) + time.sleep(15) feature_retrieval_job = client.get_batch_features( - entity_rows=entity_df, feature_refs=[f"{PROJECT_NAME}/feature_value6:1", f"{PROJECT_NAME}/other_feature_value7:1"] + entity_rows=features_8_df[["datetime", "entity_id"]], + feature_refs=["feature_value8"], + project=PROJECT_NAME, ) + output = feature_retrieval_job.to_dataframe() + clean_up_remote_files(feature_retrieval_job.get_avro_files()) print(output.head()) - assert output["entity_id"].to_list() == [int(i) for i in output["feature_value6"].to_list()] - assert output["other_entity_id"].to_list() == output["other_feature_value7"].to_list() + assert output["entity_id"].to_list() == output["feature_value8"].to_list() + + +@pytest.fixture(scope="module", autouse=True) +def infra_teardown(pytestconfig, core_url, serving_url): + client = Client(core_url=core_url, serving_url=serving_url) + client.set_project(PROJECT_NAME) + + marker = pytestconfig.getoption("-m") + yield marker + if marker == "dataflow_runner": + ingest_jobs = client.list_ingest_jobs() + ingest_jobs = [ + client.list_ingest_jobs(job.id)[0].external_id + for job in ingest_jobs + if job.status == IngestionJobStatus.RUNNING + ] + + cwd = os.getcwd() + with open(f"{cwd}/ingesting_jobs.txt", "w+") as output: + for job in ingest_jobs: + output.write("%s\n" % job) + else: + print("Cleaning up not required") + + + +''' +This suite of tests tests the apply feature set - update feature set - retrieve +event sequence. It ensures that when a feature set is updated, tombstoned features +are no longer retrieved, and added features are null for previously ingested +rows. + +It is marked separately because of the length of time required +to perform this test, due to bigquery schema caching for streaming writes. +''' + +@pytest.fixture(scope="module") +def update_featureset_dataframe(): + n_rows = 10 + time_offset = datetime.utcnow().replace(tzinfo=pytz.utc) + return pd.DataFrame( + { + "datetime": [time_offset] * n_rows, + "entity_id": [i for i in range(n_rows)], + "update_feature1": ["a" for i in range(n_rows)], + "update_feature2": [i + 2 for i in range(n_rows)], + "update_feature3": [i for i in range(n_rows)], + "update_feature4": ["b" for i in range(n_rows)], + } + ) + + +@pytest.mark.fs_update +@pytest.mark.run(order=20) +def test_update_featureset_apply_featureset_and_ingest_first_subset( + client, update_featureset_dataframe +): + subset_columns = ["datetime", "entity_id", "update_feature1", "update_feature2"] + subset_df = update_featureset_dataframe.iloc[:5][subset_columns] + update_fs = FeatureSet( + "update_fs", + entities=[Entity(name="entity_id", dtype=ValueType.INT64)], + max_age=Duration(seconds=432000), + ) + update_fs.infer_fields_from_df(subset_df) + client.apply(update_fs) + + client.ingest(feature_set=update_fs, source=subset_df) + + time.sleep(15) + feature_retrieval_job = client.get_batch_features( + entity_rows=update_featureset_dataframe[["datetime", "entity_id"]].iloc[:5], + feature_refs=[ + "update_feature1", + "update_feature2", + ], + project=PROJECT_NAME + ) + + output = feature_retrieval_job.to_dataframe().sort_values(by=["entity_id"]) + clean_up_remote_files(feature_retrieval_job.get_avro_files()) + print(output.head()) + + assert output["update_feature1"].to_list() == subset_df["update_feature1"].to_list() + assert output["update_feature2"].to_list() == subset_df["update_feature2"].to_list() + + +@pytest.mark.fs_update +@pytest.mark.timeout(600) +@pytest.mark.run(order=21) +def test_update_featureset_update_featureset_and_ingest_second_subset( + client, update_featureset_dataframe +): + subset_columns = [ + "datetime", + "entity_id", + "update_feature1", + "update_feature3", + "update_feature4", + ] + subset_df = update_featureset_dataframe.iloc[5:][subset_columns] + update_fs = FeatureSet( + "update_fs", + entities=[Entity(name="entity_id", dtype=ValueType.INT64)], + max_age=Duration(seconds=432000), + ) + update_fs.infer_fields_from_df(subset_df) + client.apply(update_fs) + + # We keep retrying this ingestion until all values make it into the buffer. + # This is a necessary step because bigquery streaming caches table schemas + # and as a result, rows may be lost. + while True: + ingestion_id = client.ingest(feature_set=update_fs, source=subset_df) + time.sleep(15) # wait for rows to get written to bq + rows_ingested = get_rows_ingested(client, update_fs, ingestion_id) + if rows_ingested == len(subset_df): + print(f"Number of rows successfully ingested: {rows_ingested}. Continuing.") + break + print( + f"Number of rows successfully ingested: {rows_ingested}. Retrying ingestion." + ) + time.sleep(30) + + feature_retrieval_job = client.get_batch_features( + entity_rows=update_featureset_dataframe[["datetime", "entity_id"]].iloc[5:], + feature_refs=[ + "update_feature1", + "update_feature3", + "update_feature4", + ], + project=PROJECT_NAME, + ) + + output = feature_retrieval_job.to_dataframe().sort_values(by=["entity_id"]) + clean_up_remote_files(feature_retrieval_job.get_avro_files()) + print(output.head()) + + assert output["update_feature1"].to_list() == subset_df["update_feature1"].to_list() + assert output["update_feature3"].to_list() == subset_df["update_feature3"].to_list() + assert output["update_feature4"].to_list() == subset_df["update_feature4"].to_list() + + +@pytest.mark.fs_update +@pytest.mark.run(order=22) +def test_update_featureset_retrieve_all_fields(client, update_featureset_dataframe): + with pytest.raises(Exception): + feature_retrieval_job = client.get_batch_features( + entity_rows=update_featureset_dataframe[["datetime", "entity_id"]], + feature_refs=[ + "update_feature1", + "update_feature2", + "update_feature3", + "update_feature4", + ], + project=PROJECT_NAME, + ) + feature_retrieval_job.result() + + +@pytest.mark.fs_update +@pytest.mark.run(order=23) +def test_update_featureset_retrieve_valid_fields(client, update_featureset_dataframe): + feature_retrieval_job = client.get_batch_features( + entity_rows=update_featureset_dataframe[["datetime", "entity_id"]], + feature_refs=[ + "update_feature1", + "update_feature3", + "update_feature4", + ], + project=PROJECT_NAME, + ) + output = feature_retrieval_job.to_dataframe().sort_values(by=["entity_id"]) + clean_up_remote_files(feature_retrieval_job.get_avro_files()) + print(output.head(10)) + assert ( + output["update_feature1"].to_list() + == update_featureset_dataframe["update_feature1"].to_list() + ) + # we have to convert to float because the column contains np.NaN + assert [math.isnan(i) for i in output["update_feature3"].to_list()[:5]] == [ + True + ] * 5 + assert output["update_feature3"].to_list()[5:] == [ + float(i) for i in update_featureset_dataframe["update_feature3"].to_list()[5:] + ] + assert ( + output["update_feature4"].to_list() + == [None] * 5 + update_featureset_dataframe["update_feature4"].to_list()[5:] + ) + + +def get_rows_ingested( + client: Client, feature_set: FeatureSet, ingestion_id: str +) -> int: + response = client._core_service_stub.ListStores( + ListStoresRequest(filter=ListStoresRequest.Filter(name="historical")) + ) + bq_config = response.store[0].bigquery_config + project = bq_config.project_id + dataset = bq_config.dataset_id + table = f"{PROJECT_NAME}_{feature_set.name}" + + bq_client = bigquery.Client(project=project) + rows = bq_client.query( + f'SELECT COUNT(*) as count FROM `{project}.{dataset}.{table}` WHERE ingestion_id = "{ingestion_id}"' + ).result() + + for row in rows: + return row["count"] + + +def clean_up_remote_files(files): + storage_client = storage.Client() + for file_uri in files: + if file_uri.scheme == "gs": + blob = Blob.from_string(file_uri.geturl(), client=storage_client) + blob.delete() +