From bbcbb201560b4c7b12f28e7fe3ef21ac056cfb39 Mon Sep 17 00:00:00 2001 From: Terence Lim Xinping Date: Thu, 10 Sep 2020 02:36:56 +0000 Subject: [PATCH 01/36] GitBook: [master] 34 pages modified --- docs/administration/upgrading.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/administration/upgrading.md b/docs/administration/upgrading.md index 33c311cf4de..4fe83bc0923 100644 --- a/docs/administration/upgrading.md +++ b/docs/administration/upgrading.md @@ -4,14 +4,17 @@ ### Feast Core Validation changes -In v0.7, Feast Core no longer accepts dash in names in: +In v0.7, Feast Core no longer accepts starting with number \(0-9\) and using dash in names for: * Project * Feature Set * Entities * Features -Migrate all project, feature sets, entities, feature names with ‘-’ by recreating them with '-' replace with '\_' +Migrate all project, feature sets, entities, feature names: + +* with ‘-’ by recreating them with '-' replace with '\_' +* recreate any names with a number \(0-9\) as the first letter to one without. Feast now prevents feature sets from being applied if no store is subscribed to that Feature Set. @@ -27,14 +30,11 @@ In v0.7, the following changes are made to the Ingestion Job API: * Changed List Ingestion Job API to return list of `FeatureSetReference` instead of list of FeatureSet in response. * Moved `ListIngestionJobs`, `StopIngestionJob`, `RestartIngestionJob` calls from `CoreService` to `JobControllerService`. -* Python SDK/CLI: Added new J[ob Controller client ](https://github.com/feast-dev/feast/blob/master/sdk/python/feast/contrib/job_controller/client.py)and `jobcontroller_url` config option. +* Python SDK/CLI: Added new [Job Controller client ](https://github.com/feast-dev/feast/blob/master/sdk/python/feast/contrib/job_controller/client.py)and `jobcontroller_url` config option. Users of the Ingestion Job API via gRPC should migrate by: -* Add new client to connect to Job Controller endpoint to call `JobControllerService` and call `ListIngestionJobs`, `StopIngestionJob`, `RestartIngestionJob` - - from new client. - +* Add new client to connect to Job Controller endpoint to call `JobControllerService` and call `ListIngestionJobs`, `StopIngestionJob`, `RestartIngestionJob` from new client. * Migrate code to accept feature references instead of feature sets returned in `ListIngestionJobs` response. Users of Ingestion Job via Python SDK \(ie `feast ingest-jobs list` or `client.stop_ingest_job()` etc.\) should migrate by: @@ -44,9 +44,9 @@ Users of Ingestion Job via Python SDK \(ie `feast ingest-jobs list` or `client.s ### Configuration Properties Changes -* Rename `feast.jobs.consolidate-jobs-per-source property` to `feast.jobs.controller. consolidate-jobs-per-sources` -* Renamed Subject claim property from `feast.security.authorization.options.subjectClaim` to `feast.security.authentication.options.subjectClaim` -* `Rename feast.logging.audit.messageLoggingEnabled` to `feast.audit.mesageLogging.enabled.` +* Rename `feast.jobs.consolidate-jobs-per-source property` to `feast.jobs.controller.consolidate-jobs-per-sources` +* Rename`feast.security.authorization.options.subjectClaim` to `feast.security.authentication.options.subjectClaim` +* Rename `feast.logging.audit.messageLoggingEnabled` to `feast.audit.messageLogging.enabled` ## Migration v0.5 to v0.6 From e0dfb672455b1cc186b8862b114708831c656734 Mon Sep 17 00:00:00 2001 From: "Michael Hirn (MJ)" Date: Thu, 10 Sep 2020 06:43:17 +0100 Subject: [PATCH 02/36] Add community SDK section to API docs (#992) including reference to community-provided Node.js SDK --- docs/reference/api.md | 6 ++++++ docs/reference/api/README.md | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/docs/reference/api.md b/docs/reference/api.md index c30736dcc2d..b7fa8041a91 100644 --- a/docs/reference/api.md +++ b/docs/reference/api.md @@ -8,3 +8,9 @@ The following API docs are available * [Go Client SDK](https://godoc.org/github.com/feast-dev/feast/sdk/go): This SDK is used for the retrieval of online features from Feast. * [Python SDK](https://api.docs.feast.dev/python/): This is the complete reference to the Feast Python SDK. The SDK is used to manage feature sets, features, jobs, projects, and entities. It can also be used to retrieve training datasets or online features from Feast Serving. +### Community Contributions + +The following community provided SDKs are available + +* [Node.js SDK](https://github.com/MichaelHirn/feast-client/): A Node.js SDK written in TypeScript. The SDK is used to manage feature sets, features, jobs, projects, and entities. + diff --git a/docs/reference/api/README.md b/docs/reference/api/README.md index cd3d4545dd2..54135d6c565 100644 --- a/docs/reference/api/README.md +++ b/docs/reference/api/README.md @@ -9,3 +9,8 @@ The following API docs are available * [Java Client SDK](https://javadoc.io/doc/dev.feast/feast-sdk): The Java library used for the retrieval of online features from Feast. * [Python SDK](https://api.docs.feast.dev/python/): This is the complete reference to the Feast Python SDK. The SDK is used to manage feature sets, features, jobs, projects, and entities. It can also be used to retrieve training datasets or online features from Feast Serving. +### Community Contributions + +The following community provided SDKs are available + +* [Node.js SDK](https://github.com/MichaelHirn/feast-client/): A Node.js SDK written in TypeScript. The SDK is used to manage feature sets, features, jobs, projects, and entities. From 239e1915f1b67b46d373830b898519701b1f686a Mon Sep 17 00:00:00 2001 From: Zhu Zhan Yan Date: Thu, 10 Sep 2020 17:45:00 +0800 Subject: [PATCH 03/36] Fix Github Actions versioned image push (#994) * Fix push semver versioned ci image not being pushed if has semver suffix. * Add comments to document semver regex. * Replace regular expression matcher with grep -P for Perl regex style matching. --- .github/workflows/master_only.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/master_only.yml b/.github/workflows/master_only.yml index cf058914d4e..501630a318b 100644 --- a/.github/workflows/master_only.yml +++ b/.github/workflows/master_only.yml @@ -42,8 +42,10 @@ jobs: - name: Push versioned release run: | # Build and push semver tagged commits - rx='^v[0-9]+?\.[0-9]+?\.[0-9]+?$' - if [[ "${RELEASE_VERSION}" =~ $rx ]]; then + # Regular expression should match MAJOR.MINOR.PATCH[-PRERELEASE[.IDENTIFIER]] + # eg. v0.7.1 v0.7.2-alpha v0.7.2-rc.1 + rx='^v([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))$ ' + if echo "${RELEASE_VERSION}" | grep -P "$rx" &>/dev/null ; then VERSION_WITHOUT_PREFIX=${RELEASE_VERSION:1} docker tag gcr.io/kf-feast/feast-${{ matrix.component }}:${GITHUB_SHA} gcr.io/kf-feast/feast-${{ matrix.component }}:${VERSION_WITHOUT_PREFIX} From 7fa3b077fc617821cdc99146ea6cc344af4d6ea3 Mon Sep 17 00:00:00 2001 From: Zhu Zhan Yan Date: Fri, 11 Sep 2020 15:54:00 +0800 Subject: [PATCH 04/36] Add Branch and RC Awareness to Version Lint & Fix Semver Regex (#998) * Update version lint script to detect maven, docker image and stable version . * Allow users to lint versions against their merge branch of choice by branch TARGET_MERGE_BRANCH * Update development image tag version from 'dev' to 'develop' to make it easier to lint version. * Update lint version script to only echo file contents when version is wrong. * Update lint script to flag all failures instead of just one. * Remove file contents output from lint version script. * Update lint version script to make output more concise and readable. * Use develop images instead of stable images in master deployments. * Update outdated documentation in serving readme. * Fix outdated version in datatypes java README.md and add version lint check. * Add version lint checks to documentation. * Fix semver regex in github actions workflow not matching suffixless version tags. * Add version lint check for latest stable version in changelog. * Update lint-version script use tool agnostic variable names. * Fix typo. --- .github/workflows/master_only.yml | 12 +- datatypes/java/README.md | 2 +- docs/contributing/development-guide.md | 2 +- .../charts/feast/charts/feast-core/README.md | 2 +- .../feast/charts/feast-core/values.yaml | 2 +- .../charts/feast-jobcontroller/README.md | 2 +- .../charts/feast-jobcontroller/values.yaml | 2 +- .../feast/charts/feast-jupyter/README.md | 2 +- .../feast/charts/feast-jupyter/values.yaml | 4 +- .../feast/charts/feast-serving/README.md | 2 +- .../feast/charts/feast-serving/values.yaml | 2 +- infra/charts/feast/requirements.lock | 8 +- infra/docker-compose/.env.sample | 4 +- infra/scripts/validate-version-consistency.sh | 109 +++++++++++++----- serving/README.md | 2 +- 15 files changed, 104 insertions(+), 53 deletions(-) diff --git a/.github/workflows/master_only.yml b/.github/workflows/master_only.yml index 501630a318b..edbcacceeac 100644 --- a/.github/workflows/master_only.yml +++ b/.github/workflows/master_only.yml @@ -31,21 +31,21 @@ jobs: run: make build-${{ matrix.component }}-docker REGISTRY=gcr.io/kf-feast VERSION=${GITHUB_SHA} - name: Push image run: make push-${{ matrix.component }}-docker REGISTRY=gcr.io/kf-feast VERSION=${GITHUB_SHA} - - name: Push image to feast dev + - name: Push development Docker image run: | if [ ${GITHUB_REF#refs/*/} == "master" ]; then - docker tag gcr.io/kf-feast/feast-${{ matrix.component }}:${GITHUB_SHA} gcr.io/kf-feast/feast-${{ matrix.component }}:dev - docker push gcr.io/kf-feast/feast-${{ matrix.component }}:dev + docker tag gcr.io/kf-feast/feast-${{ matrix.component }}:${GITHUB_SHA} gcr.io/kf-feast/feast-${{ matrix.component }}:develop + docker push gcr.io/kf-feast/feast-${{ matrix.component }}:develop fi - name: Get version run: echo ::set-env name=RELEASE_VERSION::${GITHUB_REF#refs/*/} - - name: Push versioned release + - name: Push versioned Docker image run: | # Build and push semver tagged commits # Regular expression should match MAJOR.MINOR.PATCH[-PRERELEASE[.IDENTIFIER]] # eg. v0.7.1 v0.7.2-alpha v0.7.2-rc.1 - rx='^v([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))$ ' - if echo "${RELEASE_VERSION}" | grep -P "$rx" &>/dev/null ; then + SEMVER_REGEX='^v[0-9]+\.[0-9]+\.[0-9]+(-([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$' + if echo "${RELEASE_VERSION}" | grep -P "$SEMVER_REGEX" &>/dev/null ; then VERSION_WITHOUT_PREFIX=${RELEASE_VERSION:1} docker tag gcr.io/kf-feast/feast-${{ matrix.component }}:${GITHUB_SHA} gcr.io/kf-feast/feast-${{ matrix.component }}:${VERSION_WITHOUT_PREFIX} diff --git a/datatypes/java/README.md b/datatypes/java/README.md index f87a50f1bd1..5a5deb04296 100644 --- a/datatypes/java/README.md +++ b/datatypes/java/README.md @@ -16,7 +16,7 @@ Dependency Coordinates dev.feast datatypes-java - 0.4.0-SNAPSHOT + 0.7-SNAPSHOT ``` diff --git a/docs/contributing/development-guide.md b/docs/contributing/development-guide.md index e71c91d00a3..2463fa48104 100644 --- a/docs/contributing/development-guide.md +++ b/docs/contributing/development-guide.md @@ -210,7 +210,7 @@ grpc_cli call localhost:6566 GetFeastServingInfo '' ```text connecting to localhost:6566 -version: "0.6.2-SNAPSHOT" +version: "0.7-SNAPSHOT" type: FEAST_SERVING_TYPE_ONLINE Rpc succeeded with OK status diff --git a/infra/charts/feast/charts/feast-core/README.md b/infra/charts/feast/charts/feast-core/README.md index 36d963dd242..af9370dd72c 100644 --- a/infra/charts/feast/charts/feast-core/README.md +++ b/infra/charts/feast/charts/feast-core/README.md @@ -23,7 +23,7 @@ Current chart version is `0.7-SNAPSHOT` | 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 | `"0.6.2"` | Image tag | +| image.tag | string | `"develop"` | 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 | diff --git a/infra/charts/feast/charts/feast-core/values.yaml b/infra/charts/feast/charts/feast-core/values.yaml index e25dba9291c..28ab8a1deeb 100644 --- a/infra/charts/feast/charts/feast-core/values.yaml +++ b/infra/charts/feast/charts/feast-core/values.yaml @@ -5,7 +5,7 @@ image: # image.repository -- Docker image repository repository: gcr.io/kf-feast/feast-core # image.tag -- Image tag - tag: 0.6.2 + tag: develop # image.pullPolicy -- Image pull policy pullPolicy: IfNotPresent diff --git a/infra/charts/feast/charts/feast-jobcontroller/README.md b/infra/charts/feast/charts/feast-jobcontroller/README.md index 7c27fccedf6..628a69f480f 100644 --- a/infra/charts/feast/charts/feast-jobcontroller/README.md +++ b/infra/charts/feast/charts/feast-jobcontroller/README.md @@ -23,7 +23,7 @@ Current chart version is `0.7-SNAPSHOT` | 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-jobcontroller"` | Docker image repository | -| image.tag | string | `"0.6.2"` | Image tag | +| image.tag | string | `"develop"` | 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 | diff --git a/infra/charts/feast/charts/feast-jobcontroller/values.yaml b/infra/charts/feast/charts/feast-jobcontroller/values.yaml index bd4856ad54a..f57c98032c5 100644 --- a/infra/charts/feast/charts/feast-jobcontroller/values.yaml +++ b/infra/charts/feast/charts/feast-jobcontroller/values.yaml @@ -5,7 +5,7 @@ image: # image.repository -- Docker image repository repository: gcr.io/kf-feast/feast-jobcontroller # image.tag -- Image tag - tag: 0.6.2 + tag: develop # image.pullPolicy -- Image pull policy pullPolicy: IfNotPresent diff --git a/infra/charts/feast/charts/feast-jupyter/README.md b/infra/charts/feast/charts/feast-jupyter/README.md index c35da52d824..ec9567190df 100644 --- a/infra/charts/feast/charts/feast-jupyter/README.md +++ b/infra/charts/feast/charts/feast-jupyter/README.md @@ -17,5 +17,5 @@ Current chart version is `0.7-SNAPSHOT` | gcpServiceAccount.existingSecret.name | string | `"feast-gcp-service-account"` | Name of the existing secret containing the service account | | image.pullPolicy | string | `"Always"` | Image pull policy | | image.repository | string | `"gcr.io/kf-feast/feast-jupyter"` | Docker image repository | -| image.tag | string | `"0.6.2"` | Image tag | +| image.tag | string | `"develop"` | Image tag | | replicaCount | int | `1` | Number of pods that will be created | diff --git a/infra/charts/feast/charts/feast-jupyter/values.yaml b/infra/charts/feast/charts/feast-jupyter/values.yaml index 162bb9bf17f..b4a42b0c18d 100644 --- a/infra/charts/feast/charts/feast-jupyter/values.yaml +++ b/infra/charts/feast/charts/feast-jupyter/values.yaml @@ -5,7 +5,7 @@ image: # image.repository -- Docker image repository repository: gcr.io/kf-feast/feast-jupyter # image.tag -- Image tag - tag: 0.6.2 + tag: develop # image.pullPolicy -- Image pull policy pullPolicy: Always @@ -16,4 +16,4 @@ gcpServiceAccount: # 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 \ No newline at end of file + key: credentials.json diff --git a/infra/charts/feast/charts/feast-serving/README.md b/infra/charts/feast/charts/feast-serving/README.md index ad8862e1743..16f39ef5694 100644 --- a/infra/charts/feast/charts/feast-serving/README.md +++ b/infra/charts/feast/charts/feast-serving/README.md @@ -23,7 +23,7 @@ Current chart version is `0.7-SNAPSHOT` | 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 | `"0.6.2"` | Image tag | +| image.tag | string | `"develop"` | 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 | diff --git a/infra/charts/feast/charts/feast-serving/values.yaml b/infra/charts/feast/charts/feast-serving/values.yaml index 220588156ee..6343f4432b6 100644 --- a/infra/charts/feast/charts/feast-serving/values.yaml +++ b/infra/charts/feast/charts/feast-serving/values.yaml @@ -5,7 +5,7 @@ image: # image.repository -- Docker image repository repository: gcr.io/kf-feast/feast-serving # image.tag -- Image tag - tag: 0.6.2 + tag: develop # image.pullPolicy -- Image pull policy pullPolicy: IfNotPresent diff --git a/infra/charts/feast/requirements.lock b/infra/charts/feast/requirements.lock index 884e4c75270..9844cc048c0 100644 --- a/infra/charts/feast/requirements.lock +++ b/infra/charts/feast/requirements.lock @@ -1,16 +1,16 @@ dependencies: - name: feast-core repository: "" - version: 0.6.2 + version: 0.7-SNAPSHOT - name: feast-serving repository: "" - version: 0.6.2 + version: 0.7-SNAPSHOT - name: feast-serving repository: "" - version: 0.6.2 + version: 0.7-SNAPSHOT - name: feast-jupyter repository: "" - version: 0.6.2 + version: 0.7-SNAPSHOT - name: postgresql repository: https://kubernetes-charts.storage.googleapis.com/ version: 8.6.1 diff --git a/infra/docker-compose/.env.sample b/infra/docker-compose/.env.sample index 4dcca0b60e6..e984460f8be 100644 --- a/infra/docker-compose/.env.sample +++ b/infra/docker-compose/.env.sample @@ -1,8 +1,8 @@ COMPOSE_PROJECT_NAME=feast -FEAST_VERSION=0.6.2 +FEAST_VERSION=develop GCP_SERVICE_ACCOUNT=./gcp-service-accounts/key.json FEAST_CORE_CONFIG=./core/core.yml FEAST_JOB_CONTROLLER_CONFIG=./jobcontroller/jobcontroller.yml FEAST_HISTORICAL_SERVING_CONFIG=./serving/historical-serving.yml FEAST_HISTORICAL_SERVING_ENABLED=false -FEAST_ONLINE_SERVING_CONFIG=./serving/online-serving.yml \ No newline at end of file +FEAST_ONLINE_SERVING_CONFIG=./serving/online-serving.yml diff --git a/infra/scripts/validate-version-consistency.sh b/infra/scripts/validate-version-consistency.sh index f33cb8a56f4..2bb019dc8a2 100755 --- a/infra/scripts/validate-version-consistency.sh +++ b/infra/scripts/validate-version-consistency.sh @@ -1,80 +1,131 @@ #!/usr/bin/env bash # This script will scan through a list of files to validate that all versions are consistent with -# - Master version (could be snapshot) -# - Highest stable commit (latest tag) +# - Master version: version set in maven (could be snapshot) +# - Release version: 'dev' on master, Lastest tag on release branches. +# - Stable Version: Highest stable tag. release candidates not included. +# Usage: ./validate-version-consistency.sh +# Optionaly set TARGET_MERGE_BRANCH var to the target merge branch to lint +# versions against the given merge branch. set -e +BRANCH_NAME=${TARGET_MERGE_BRANCH-$(git rev-parse --abbrev-ref HEAD)} +# Matches (ie vMAJOR.MINOR-branch) release branch names +RELEASE_BRANCH_REGEX="^v[0-9]+\.[0-9]+-branch$" + # Determine the current Feast version from Maven (pom.xml) export FEAST_MASTER_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) [[ -z "$FEAST_MASTER_VERSION" ]] && { echo "$FEAST_MASTER_VERSION is missing, please check pom.xml and maven" exit 1 } +echo "Linting Master Version: $FEAST_MASTER_VERSION" -# Determine the highest released version from Git history -git fetch --prune --unshallow --tags || true -FEAST_RELEASE_VERSION_WITH_V=$(git tag -l --sort -version:refname | head -n 1) -echo $FEAST_RELEASE_VERSION_WITH_V - -export FEAST_RELEASE_VERSION=${FEAST_RELEASE_VERSION_WITH_V#"v"} -echo $FEAST_RELEASE_VERSION - +# Determine Last release tag relative to current branch +if [ $BRANCH_NAME = "master" ] +then + # Use development version + FEAST_RELEASE_VERSION="develop" +elif echo "$BRANCH_NAME" | grep -P $RELEASE_BRANCH_REGEX &>/dev/null +then + # Use last release tag tagged on the release branch + LAST_MERGED_TAG=$(git tag -l --sort -version:refname --merged | head -n 1) + FEAST_MASTER_VERSION=${LAST_MERGED_TAG#"v"} +else + # Do not enforce version linting as we don't know if the target merge branch FEAST_RELEASE_VERSION="_ANY" + FEAST_RELEASE_VERSION="_ANY" + echo "WARNING: Skipping docker version lint" +fi [[ -z "$FEAST_RELEASE_VERSION" ]] && { echo "FEAST_RELEASE_VERSION is missing" exit 1 } +export FEAST_RELEASE_VERSION +echo "Linting Release Version: $FEAST_RELEASE_VERSION" + +# Determine highest stable version (no release candidates) relative to current branch. +# Regular expression for matching stable tags in the format vMAJOR.MINOR.PATCH +STABLE_TAG_REGEX="^v[0-9]+\.[0-9]+\.[0-9]+$" +if [ $BRANCH_NAME = "master" ] +then + # Use last stable tag repo wide + LAST_STABLE_TAG=$(git tag --sort -version:refname | grep -P "$STABLE_TAG_REGEX" | head -n 1) + FEAST_STABLE_VERSION=${LAST_STABLE_TAG#"v"} +elif echo "$BRANCH_NAME" | grep -P $RELEASE_BRANCH_REGEX &>/dev/null +then + # Use last stable tag tagged on the release branch + LAST_STABLE_MERGE_TAG=$(git tag --sort -version:refname --merged | grep -P "$STABLE_TAG_REGEX" | head -n 1) + FEAST_STABLE_VERSION=${LAST_STABLE_MERGE_TAG#"v"} +else + # Do not enforce version linting as we don't know if the target merge branch + FEAST_STABLE_VERSION="_ANY" + echo "WARNING: Skipping stable version lint" +fi +[[ -z "$FEAST_STABLE_VERSION" ]] && { + echo "FEAST_STABLE_VERSION is missing" + exit 1 +} +export FEAST_STABLE_VERSION +echo "Linting Stable Version: $FEAST_STABLE_VERSION" # List of files to validate with master version (from pom.xml) # Structure is a comma separated list of structure # , , -# declare -a files_to_validate_version=( "infra/charts/feast/Chart.yaml,1,${FEAST_MASTER_VERSION}" "infra/charts/feast/charts/feast-core/Chart.yaml,1,${FEAST_MASTER_VERSION}" "infra/charts/feast/charts/feast-core/values.yaml,1,${FEAST_RELEASE_VERSION}" + "infra/charts/feast/charts/feast-core/README.md,1,${FEAST_MASTER_VERSION}" "infra/charts/feast/charts/feast-core/README.md,1,${FEAST_RELEASE_VERSION}" "infra/charts/feast/charts/feast-serving/Chart.yaml,1,${FEAST_MASTER_VERSION}" "infra/charts/feast/charts/feast-jupyter/values.yaml,1,${FEAST_RELEASE_VERSION}" "infra/charts/feast/charts/feast-jupyter/README.md,1,${FEAST_RELEASE_VERSION}" + "infra/charts/feast/charts/feast-jupyter/README.md,1,${FEAST_MASTER_VERSION}" "infra/charts/feast/charts/feast-jupyter/Chart.yaml,1,${FEAST_MASTER_VERSION}" "infra/charts/feast/charts/feast-serving/values.yaml,1,${FEAST_RELEASE_VERSION}" "infra/charts/feast/charts/feast-serving/README.md,1,${FEAST_RELEASE_VERSION}" + "infra/charts/feast/charts/feast-serving/README.md,1,${FEAST_MASTER_VERSION}" "infra/charts/feast/charts/feast-jobcontroller/Chart.yaml,1,${FEAST_MASTER_VERSION}" "infra/charts/feast/charts/feast-jobcontroller/values.yaml,1,${FEAST_RELEASE_VERSION}" + "infra/charts/feast/charts/feast-jobcontroller/README.md,1,${FEAST_MASTER_VERSION}" "infra/charts/feast/charts/feast-jobcontroller/README.md,1,${FEAST_RELEASE_VERSION}" "infra/charts/feast/requirements.yaml,4,${FEAST_MASTER_VERSION}" - "infra/charts/feast/requirements.lock,4,${FEAST_RELEASE_VERSION}" + "infra/charts/feast/requirements.lock,4,${FEAST_MASTER_VERSION}" "infra/docker-compose/.env.sample,1,${FEAST_RELEASE_VERSION}" + "datatypes/java/README.md,1,${FEAST_MASTER_VERSION}" + "docs/contributing/development-guide.md,4,${FEAST_MASTER_VERSION}" + "docs/administration/audit-logging.md,1,${FEAST_STABLE_VERSION}" + "docs/getting-started/deploying-feast/docker-compose.md,1,${FEAST_STABLE_VERSION}" + "docs/getting-started/deploying-feast/kubernetes.md,1,${FEAST_STABLE_VERSION}" + "README.md,1,${FEAST_STABLE_VERSION}" + "CHANGELOG.md,2,${FEAST_STABLE_VERSION}" ) echo echo "Testing list of files to ensure they have the correct version" echo + +IS_LINT_SUCCESS=true for i in "${files_to_validate_version[@]}"; do IFS=',' read -r FILE_PATH EXPECTED_OCCURRENCES VERSION <<<"${i}" - echo - echo - echo "Testing whether versions are correctly set within file: $FILE_PATH" - echo - echo "File contents:" - echo "=========================================================" - cat "$FILE_PATH" - echo + # Disable version lint if '_ANY' specified as version. + if [ "$VERSION" = "_ANY" ] + then + continue + fi + echo "=========================================================" - ACTUAL_OCCURRENCES=$(grep -c "$VERSION" "$FILE_PATH" || true) + echo "Testing whether versions are correctly set within file: $FILE_PATH" + ACTUAL_OCCURRENCES=$(grep -c -P "\bv?$VERSION\b" "$FILE_PATH" || true) if [ "${ACTUAL_OCCURRENCES}" -eq "${EXPECTED_OCCURRENCES}" ]; then - echo "SUCCESS" - echo - echo "Expecting $EXPECTED_OCCURRENCES occurrences of $VERSION in $FILE_PATH, and found $ACTUAL_OCCURRENCES" + echo "OK: Expecting $EXPECTED_OCCURRENCES occurrences of '$VERSION' in $FILE_PATH, and found $ACTUAL_OCCURRENCES" else - echo "FAILURE" - echo - echo "Expecting $EXPECTED_OCCURRENCES occurrences of $VERSION in $FILE_PATH, but found $ACTUAL_OCCURRENCES" - exit 1 + echo "FAIL: Expecting $EXPECTED_OCCURRENCES occurrences of '$VERSION' in $FILE_PATH, but found $ACTUAL_OCCURRENCES" + IS_LINT_SUCCESS=false fi - echo "=========================================================" done + +if $IS_LINT_SUCCESS; then exit 0; else exit 1; fi diff --git a/serving/README.md b/serving/README.md index 39eef311033..ab530bb60df 100644 --- a/serving/README.md +++ b/serving/README.md @@ -11,8 +11,8 @@ From the Feast project root directory, run the following Maven command to start ```bash # Assumptions: # - Local Feast Core is running on localhost:6565 +# Uses configuration from serving/src/main/resources/application.yml mvn -pl serving spring-boot:run -Dspring-boot.run.arguments=\ ---feast.store.config-path=./sample_redis_config.yml,\ --feast.core-host=localhost,\ --feast.core-port=6565 ``` From 49309788c37f8ac2c5574a69f2d065d50a484dc0 Mon Sep 17 00:00:00 2001 From: Zhu Zhan Yan Date: Fri, 11 Sep 2020 16:21:00 +0800 Subject: [PATCH 05/36] Fix lint version not pulling tags. (#999) --- infra/scripts/validate-version-consistency.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/infra/scripts/validate-version-consistency.sh b/infra/scripts/validate-version-consistency.sh index 2bb019dc8a2..896de18b14e 100755 --- a/infra/scripts/validate-version-consistency.sh +++ b/infra/scripts/validate-version-consistency.sh @@ -9,7 +9,10 @@ # versions against the given merge branch. set -e +# Fetch tags and current branch +git fetch --prune --unshallow --tags || true BRANCH_NAME=${TARGET_MERGE_BRANCH-$(git rev-parse --abbrev-ref HEAD)} + # Matches (ie vMAJOR.MINOR-branch) release branch names RELEASE_BRANCH_REGEX="^v[0-9]+\.[0-9]+-branch$" From 4809a964442ebb0326a7ed61c21f415f18c96571 Mon Sep 17 00:00:00 2001 From: Zhu Zhan Yan Date: Mon, 14 Sep 2020 08:51:01 +0800 Subject: [PATCH 06/36] Fix Go SDK extra colon in metadata header for Authentication (#1001) * Fix Go SDK outputing extra ":" in metadata header for authentication causing authentication failure. * Fix go test --- sdk/go/auth.go | 2 +- sdk/go/auth_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/go/auth.go b/sdk/go/auth.go index 156b76e1b20..1b6703dae12 100644 --- a/sdk/go/auth.go +++ b/sdk/go/auth.go @@ -27,7 +27,7 @@ func (provider *Credential) GetRequestMetadata(ctx context.Context, uri ...strin return map[string]string{}, nil } return map[string]string{ - "Authorization": "Bearer: " + token.AccessToken, + "Authorization": "Bearer " + token.AccessToken, }, nil } diff --git a/sdk/go/auth_test.go b/sdk/go/auth_test.go index 7a6ee3d4f90..5c1711994cf 100644 --- a/sdk/go/auth_test.go +++ b/sdk/go/auth_test.go @@ -133,7 +133,7 @@ func TestCredentials(t *testing.T) { t.Errorf("Expected authentication metadata with key: '%s'", authKey) } - expectedVal := "Bearer: " + tc.want + expectedVal := "Bearer " + tc.want if meta[authKey] != expectedVal { t.Errorf("Expected authentication metadata with value: '%s' Got instead: '%s'", expectedVal, meta[authKey]) } From 3b1e606cfdc479293df35a3e9ae13071630dd736 Mon Sep 17 00:00:00 2001 From: Terence Lim Date: Wed, 16 Sep 2020 09:40:02 +0800 Subject: [PATCH 07/36] Add job-controller to test containers (#1006) --- .../test/java/feast/serving/it/BaseAuthIT.java | 6 +++++- .../it/ServingServiceOauthAuthenticationIT.java | 11 ++++++++--- .../it/ServingServiceOauthAuthorizationIT.java | 13 +++++++++---- .../docker-compose/core/application-it.yml | 8 -------- ...compose-it-core.yml => docker-compose-it.yml} | 14 ++++++++++++++ .../job-controller/application-it.yml | 16 ++++++++++++++++ 6 files changed, 52 insertions(+), 16 deletions(-) rename serving/src/test/resources/docker-compose/{docker-compose-it-core.yml => docker-compose-it.yml} (76%) create mode 100644 serving/src/test/resources/docker-compose/job-controller/application-it.yml diff --git a/serving/src/test/java/feast/serving/it/BaseAuthIT.java b/serving/src/test/java/feast/serving/it/BaseAuthIT.java index bdbe432ed8e..be327814fe3 100644 --- a/serving/src/test/java/feast/serving/it/BaseAuthIT.java +++ b/serving/src/test/java/feast/serving/it/BaseAuthIT.java @@ -30,7 +30,7 @@ public class BaseAuthIT { static final String FEATURE_NAME = "feature_1"; static final String ENTITY_ID = "entity_id"; static final String PROJECT_NAME = "project_1"; - static final int CORE_START_MAX_WAIT_TIME_IN_MINUTES = 3; + static final int SERVICE_START_MAX_WAIT_TIME_IN_MINUTES = 3; static final String CLIENT_ID = "client_id"; static final String CLIENT_SECRET = "client_secret"; static final String TOKEN_URL = "http://localhost:4444/oauth2/token"; @@ -42,6 +42,8 @@ public class BaseAuthIT { static final String CORE = "core_1"; + static final String JOB_CONTROLLER = "jobcontroller_1"; + static final String HYDRA = "hydra_1"; static final int HYDRA_PORT = 4445; @@ -51,6 +53,8 @@ public class BaseAuthIT { static final int FEAST_CORE_PORT = 6565; + static final int FEAST_JOB_CONTROLLER_PORT = 6570; + @DynamicPropertySource static void properties(DynamicPropertyRegistry registry) { registry.add("feast.stores[0].name", () -> "online"); diff --git a/serving/src/test/java/feast/serving/it/ServingServiceOauthAuthenticationIT.java b/serving/src/test/java/feast/serving/it/ServingServiceOauthAuthenticationIT.java index e50693f8ac8..476b017c6e1 100644 --- a/serving/src/test/java/feast/serving/it/ServingServiceOauthAuthenticationIT.java +++ b/serving/src/test/java/feast/serving/it/ServingServiceOauthAuthenticationIT.java @@ -67,13 +67,18 @@ public class ServingServiceOauthAuthenticationIT extends BaseAuthIT { public static DockerComposeContainer environment = new DockerComposeContainer( new File("src/test/resources/docker-compose/docker-compose-it-hydra.yml"), - new File("src/test/resources/docker-compose/docker-compose-it-core.yml")) + new File("src/test/resources/docker-compose/docker-compose-it.yml")) .withExposedService(HYDRA, HYDRA_PORT, forHttp("/health/alive").forStatusCode(200)) .withExposedService( CORE, - 6565, + FEAST_CORE_PORT, Wait.forLogMessage(".*gRPC Server started.*\\n", 1) - .withStartupTimeout(Duration.ofMinutes(CORE_START_MAX_WAIT_TIME_IN_MINUTES))); + .withStartupTimeout(Duration.ofMinutes(SERVICE_START_MAX_WAIT_TIME_IN_MINUTES))) + .withExposedService( + JOB_CONTROLLER, + FEAST_JOB_CONTROLLER_PORT, + Wait.forLogMessage(".*gRPC Server started.*\\n", 1) + .withStartupTimeout(Duration.ofMinutes(SERVICE_START_MAX_WAIT_TIME_IN_MINUTES))); @BeforeAll static void globalSetup() throws IOException, InitializationError, InterruptedException { diff --git a/serving/src/test/java/feast/serving/it/ServingServiceOauthAuthorizationIT.java b/serving/src/test/java/feast/serving/it/ServingServiceOauthAuthorizationIT.java index 5f4b169d8d2..50111fcd155 100644 --- a/serving/src/test/java/feast/serving/it/ServingServiceOauthAuthorizationIT.java +++ b/serving/src/test/java/feast/serving/it/ServingServiceOauthAuthorizationIT.java @@ -72,16 +72,21 @@ public class ServingServiceOauthAuthorizationIT extends BaseAuthIT { public static DockerComposeContainer environment = new DockerComposeContainer( new File("src/test/resources/docker-compose/docker-compose-it-hydra.yml"), - new File("src/test/resources/docker-compose/docker-compose-it-core.yml"), + new File("src/test/resources/docker-compose/docker-compose-it.yml"), new File("src/test/resources/docker-compose/docker-compose-it-keto.yml")) .withExposedService(HYDRA, HYDRA_PORT, forHttp("/health/alive").forStatusCode(200)) .withExposedService( CORE, - 6565, + FEAST_CORE_PORT, Wait.forLogMessage(".*gRPC Server started.*\\n", 1) - .withStartupTimeout(Duration.ofMinutes(CORE_START_MAX_WAIT_TIME_IN_MINUTES))) + .withStartupTimeout(Duration.ofMinutes(SERVICE_START_MAX_WAIT_TIME_IN_MINUTES))) + .withExposedService( + JOB_CONTROLLER, + FEAST_JOB_CONTROLLER_PORT, + Wait.forLogMessage(".*gRPC Server started.*\\n", 1) + .withStartupTimeout(Duration.ofMinutes(SERVICE_START_MAX_WAIT_TIME_IN_MINUTES))) .withExposedService("adaptor_1", KETO_ADAPTOR_PORT) - .withExposedService("keto_1", KETO_PORT, forHttp("/health/ready").forStatusCode(200));; + .withExposedService("keto_1", KETO_PORT, forHttp("/health/ready").forStatusCode(200)); @DynamicPropertySource static void initialize(DynamicPropertyRegistry registry) { diff --git a/serving/src/test/resources/docker-compose/core/application-it.yml b/serving/src/test/resources/docker-compose/core/application-it.yml index 35f2ee54631..1298ff99055 100644 --- a/serving/src/test/resources/docker-compose/core/application-it.yml +++ b/serving/src/test/resources/docker-compose/core/application-it.yml @@ -1,12 +1,4 @@ feast: - jobs: - polling_interval_milliseconds: 30000 - job_update_timeout_seconds: 240 - active_runner: direct - runners: - - name: direct - type: DirectRunner - options: {} stream: type: kafka options: diff --git a/serving/src/test/resources/docker-compose/docker-compose-it-core.yml b/serving/src/test/resources/docker-compose/docker-compose-it.yml similarity index 76% rename from serving/src/test/resources/docker-compose/docker-compose-it-core.yml rename to serving/src/test/resources/docker-compose/docker-compose-it.yml index bb7cdce8abb..7f994e6d9e0 100644 --- a/serving/src/test/resources/docker-compose/docker-compose-it-core.yml +++ b/serving/src/test/resources/docker-compose/docker-compose-it.yml @@ -18,6 +18,20 @@ services: - -jar - /opt/feast/feast-core.jar - --spring.config.location=classpath:/application.yml,file:/etc/feast/application.yml + + jobcontroller: + image: gcr.io/kf-feast/feast-jobcontroller:latest + volumes: + - ./job-controller/application-it.yml:/etc/feast/application.yml + depends_on: + - kafka + ports: + - 6570:6570 + command: + - java + - -jar + - /opt/feast/feast-job-controller.jar + - --spring.config.location=classpath:/application.yml,file:/etc/feast/application.yml kafka: image: confluentinc/cp-kafka:5.2.1 diff --git a/serving/src/test/resources/docker-compose/job-controller/application-it.yml b/serving/src/test/resources/docker-compose/job-controller/application-it.yml new file mode 100644 index 00000000000..118ce378726 --- /dev/null +++ b/serving/src/test/resources/docker-compose/job-controller/application-it.yml @@ -0,0 +1,16 @@ +feast: + core-host: core + jobs: + enabled: true + polling_interval_milliseconds: 30000 + job_update_timeout_seconds: 240 + active_runner: direct + runners: + - name: direct + type: DirectRunner + options: {} + stream: + type: kafka + options: + topic: feast-features + bootstrapServers: "kafka:9092,localhost:9094" \ No newline at end of file From 53e08cac762a7eb9dacaa8d235dd2bd8f42582f5 Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Wed, 16 Sep 2020 04:18:13 +0000 Subject: [PATCH 08/36] GitBook: [master] 34 pages modified --- docs/administration/audit-logging.md | 20 +++++++++ .../deploying-feast/docker-compose.md | 42 ++++++++++++------- .../deploying-feast/kubernetes.md | 16 +++---- docs/reference/api/README.md | 3 +- 4 files changed, 53 insertions(+), 28 deletions(-) diff --git a/docs/administration/audit-logging.md b/docs/administration/audit-logging.md index d695292b4dc..c11eaeba4c4 100644 --- a/docs/administration/audit-logging.md +++ b/docs/administration/audit-logging.md @@ -96,6 +96,24 @@ Fields in Transition Audit Log Type Feast currently only supports forwarding Request/Response \(Message Audit Log Type\) logs to an external fluentD service with `feast.**` Fluentd tag. +#### Request/Response Log Example + +```text +{ + "id": "45329ea9-0d48-46c5-b659-4604f6193711", + "service": "CoreService" + "status_code": "OK", + "method": "ListProjects", + "request": {}, + "response": { + "projects": [ + "default", "project1", "project2" + ] + } + "release_name": 506.457.14.512 +} +``` + #### Configuration The Fluentd Log Forwarder configured with the with the following configuration options in `application.yml`: @@ -106,3 +124,5 @@ The Fluentd Log Forwarder configured with the with the following configuration o | `feast.logging.audit.messageLogging.fluentdHost` | `localhost` | | `feast.logging.audit.messageLogging.fluentdPort` | `24224` | +When using Fluentd as the Log forwarder, a Feast `release_name` can be logged instead of the IP address \(eg. IP of Kubernetes pod deployment\), by setting an environment variable `RELEASE_NAME` when deploying Feast. + diff --git a/docs/getting-started/deploying-feast/docker-compose.md b/docs/getting-started/deploying-feast/docker-compose.md index 3b397236517..365a6c464e8 100644 --- a/docs/getting-started/deploying-feast/docker-compose.md +++ b/docs/getting-started/deploying-feast/docker-compose.md @@ -6,10 +6,6 @@ This guide will give a walk-though on deploying Feast using Docker Compose. The Docker Compose setup is recommended if you are running Feast locally to try things out. It includes a built in Jupyter Notebook Server that is preloaded with Feast example notebooks to get you started. -{% hint style="info" %} -The Docker Compose deployment will take some time fully startup. During this time you may see some connection failures which should be automatically corrected a few minutes. -{% endhint %} - ## 0. Requirements * [Docker Compose](https://docs.docker.com/compose/install/) should be installed. @@ -23,6 +19,7 @@ Clone the latest stable version of the [Feast repository](https://github.com/goj ```text git clone --depth 1 --branch v0.6.2 https://github.com/feast-dev/feast.git +export FEAST_REPO=$(pwd) cd feast/infra/docker-compose cp .env.sample .env ``` @@ -32,10 +29,24 @@ cp .env.sample .env Use Docker Compose deploy Feast for Online Serving only: ```javascript -docker-compose up -d +docker-compose up ``` -Once deployed, you should be able to connect at `localhost:8888` to the bundled Jupyer Notebook Server with example notebooks. +{% hint style="info" %} +The Docker Compose deployment will take some time fully startup: + +* During this time you may see some connection failures and container restarts which should be automatically corrected a few minutes. +* If container restarts do not stop after 10 minutes, try redeploying by + * Terminating the current deployment with `Ctrl-C` + * Deleting any attached volumes with `docker-compose down` -v + * Redeploying with `docker-compose up` +{% endhint %} + +{% hint style="info" %} + You may see `feast_historical_serving` exiting with code 1, this expected and does not affect the functionality of Feast for Online Serving. +{% endhint %} + +Once deployed, you should be able to connect at `localhost:8888` to the bundled Jupyter Notebook Server and follow in the Online Serving sections of the example notebooks: {% embed url="http://localhost:8888/tree?" caption="" %} @@ -55,12 +66,12 @@ gcloud iam service-accounts create feast-service-account gcloud projects add-iam-policy-binding my-gcp-project \ --member serviceAccount:feast-service-account@my-gcp-project.iam.gserviceaccount.com \ --role roles/editor - - gcloud iam service-accounts keys create credentials.json --iam-account \ -feast-service-account@my-gcp-project.iam.gserviceaccount.com + feast-service-account@my-gcp-project.iam.gserviceaccount.com cp credentials.json ${FEAST_REPO}/infra/docker-compose/gcp-service-accounts/key.json +# Required to prevent permissions error in Feast Jupyter: +chown 1000:1000 ${FEAST_REPO}/infra/docker-compose/gcp-service-accounts/key.json ``` Create a Google Cloud Storage Bucket that Feast will use to load data into and out of BigQuery @@ -77,11 +88,10 @@ bq --location=US mk --dataset my_project:feast ### 3.2 Configure Docker Compose -Configure the `.env` file based on your environment. At the very least you have to modify: +Configure the `.env` file under `${FEAST_REPO}/infra/docker-compose/` based on your environment. At the very least you have to modify: | Parameter | Description | | :--- | :--- | -| `GCP_SERVICE_ACCOUNT` | This should be your service account file path, for example `./gcp-service-accounts/key.json`. | | `FEAST_HISTORICAL_SERVING_ENABLED` | Set this to `true` to enable historical serving \(BigQuery\) | ### 3.3 Configure Services @@ -91,10 +101,10 @@ The following configuration has to be set in `serving/historical-serving.yml` | Parameter | Description | | :--- | :--- | | `feast.stores.config.project_id` | This is your [GCP project Id](https://cloud.google.com/resource-manager/docs/creating-managing-projects). | -| `feast.stores.config.dataset_id` | This is the name of the BigQuery dataset that you created above | -| `feast.stores.config.staging_location` | This is the staging location on Google Cloud Storage for retrieval of training datasets, created above. Make sure you append a suffix \(ie `gs://mybucket/suffix`\) | +| `feast.stores.config.dataset_id` | This is the **dataset name** of the BigQuery dataset to use. | +| `feast.stores.config.staging_location` | This is the staging location on Google Cloud Storage for retrieval of training datasets. Make sure you append a suffix \(ie `gs://mybucket/suffix`\) | -The following configuration has to be set in `core/core.yml` +The following configuration has to be set in `jobcontroller/jobcontroller.yml` | Parameter | Description | | :--- | :--- | @@ -105,10 +115,10 @@ The following configuration has to be set in `core/core.yml` Use Docker Compose deploy Feast: ```javascript -docker-compose up -d +docker-compose up ``` -Once deployed, you should be able to connect at `localhost:8888` to the bundled Jupyer Notebook Server with example notebooks. +Once deployed, you should be able to connect at `localhost:8888` to the bundled Jupyter Notebook Server with example notebooks. {% embed url="http://localhost:8888/tree?" caption="" %} diff --git a/docs/getting-started/deploying-feast/kubernetes.md b/docs/getting-started/deploying-feast/kubernetes.md index 413ac3cf749..818462b722d 100644 --- a/docs/getting-started/deploying-feast/kubernetes.md +++ b/docs/getting-started/deploying-feast/kubernetes.md @@ -61,7 +61,7 @@ Create a Kubernetes cluster: gcloud container clusters create feast-cluster \ --machine-type n1-standard-4 \ --zone us-central1-a \ - --scopes=bigquery,storage-rw,compute-ro + --scopes=bigquery,storage-rw,compute-ro ``` Create a secret in the GKE cluster from the service account `credentials.json`: @@ -99,13 +99,13 @@ Update `application-values.yml`in `values.yaml` to configure Feast based on your | Property | Description | | :--- | :--- | | `project_id` | This is your GCP Project Id. | -| `dataset_id` | This is your BigQuery dataset id. | +| `dataset_id` | This is the **dataset name** of the BigQuery dataset to use. | | `staging_location` | This is the GCS bucket used for staging data being loaded into BigQuery. | Install the Feast Helm chart to deploy Feast: ```bash -helm install --name myrelease -f values.yaml feast-charts/feast +helm install feast-release -f values.yaml feast-charts/feast ``` Wait for the Feast pods to start running and become become ready: @@ -128,15 +128,9 @@ Forwarding from 127.0.0.1:8888 -> 8888 Forwarding from [::1]:8888 -> 8888 ``` -You should now be able to open the Jupyter notebook at [http://localhost:8888/](http://localhost:8888/) +You should be able to connect at `localhost:8888` to the bundled Jupyter Notebook Server with example notebooks. -From within the Jupyter Notebook you can now clone the Feast repository - -```text -git clone --branch v0.6.2 https://github.com/feast-dev/feast -``` - -Please try out our [examples](https://github.com/feast-dev/feast/blob/master/examples/). +{% embed url="http://localhost:8888/tree?" caption="" %} ## 6. Further Reading diff --git a/docs/reference/api/README.md b/docs/reference/api/README.md index 54135d6c565..60945df68af 100644 --- a/docs/reference/api/README.md +++ b/docs/reference/api/README.md @@ -9,8 +9,9 @@ The following API docs are available * [Java Client SDK](https://javadoc.io/doc/dev.feast/feast-sdk): The Java library used for the retrieval of online features from Feast. * [Python SDK](https://api.docs.feast.dev/python/): This is the complete reference to the Feast Python SDK. The SDK is used to manage feature sets, features, jobs, projects, and entities. It can also be used to retrieve training datasets or online features from Feast Serving. -### Community Contributions +## Community Contributions The following community provided SDKs are available * [Node.js SDK](https://github.com/MichaelHirn/feast-client/): A Node.js SDK written in TypeScript. The SDK is used to manage feature sets, features, jobs, projects, and entities. + From 0738e6f3eed3982fe37a836e2a95e4a7a29168de Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Wed, 16 Sep 2020 04:18:37 +0000 Subject: [PATCH 09/36] GitBook: [master] 36 pages modified --- docs/SUMMARY.md | 1 + docs/reference/metrics-reference.md | 171 ++++++++++++++++++++++++++++ docs/user-guide/metrics.md | 55 +++++---- docs/user-guide/statistics.md | 20 ++-- 4 files changed, 214 insertions(+), 33 deletions(-) create mode 100644 docs/reference/metrics-reference.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 8d6d889ebab..7f190173a9f 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -41,6 +41,7 @@ ## Reference +* [Metrics Reference](reference/metrics-reference.md) * [Configuration Reference](reference/configuration-reference.md) * [API Reference](reference/api/README.md) * [Core gRPC API](https://api.docs.feast.dev/grpc/feast.core.pb.html) diff --git a/docs/reference/metrics-reference.md b/docs/reference/metrics-reference.md new file mode 100644 index 00000000000..67725a8a573 --- /dev/null +++ b/docs/reference/metrics-reference.md @@ -0,0 +1,171 @@ +# Metrics Reference + +Reference of the metrics that each Feast component exports: + +* [Feast Core](metrics-reference.md#feast-core) +* [Feast Serving](metrics-reference.md#feast-serving) +* [Feast Ingestion Job](metrics-reference.md#feast-ingestion-job) + +For how to configure Feast to export Metrics, see the [Metrics user guide.](../user-guide/metrics.md) + +## Feast Core + +**Exported Metrics** + +Feast Core exports the following metrics: + +| Metrics | Description | Tags | +| :--- | :--- | :--- | +| `feast_core_request_latency_seconds` | Feast Core's latency in serving Requests in Seconds. | `service`, `method`, `status_code` | +| `feast_core_feature_set_total` | No. of Feature Sets registered with Feast Core. | None | +| `feast_core_store_total` | No. of Stores registered with Feast Core. | None | +| `feast_core_max_memory_bytes` | Max amount of memory the Java virtual machine will attempt to use. | None | +| `feast_core_total_memory_bytes` | Total amount of memory in the Java virtual machine | None | +| `feast_core_free_memory_bytes` | Total amount of free memory in the Java virtual machine. | None | +| `feast_core_gc_collection_seconds` | Time spent in a given JVM garbage collector in seconds. | None | + +**Metric Tags** + +Exported Feast Core metrics may be filtered by the following tags/keys + +| Tag | Description | +| :--- | :--- | +| `service` | Name of the Service that request is made to. Should be set to `CoreService` | +| `method` | Name of the Method that the request is calling. \(ie `ListFeatureSets`\) | +| `status_code` | Status code returned as a result of handling the requests \(ie `OK`\). Can be used to find request failures. | + +## Feast Serving + +**Exported Metrics** + +Feast Serving exports the following metrics: + +| Metric | Description | Tags | +| :--- | :--- | :--- | +| `feast_serving_request_latency_seconds` | Feast Serving's latency in serving Requests in Seconds. | `method` | +| `feast_serving_request_feature_count` | No. of requests retrieving a Feature from Feast Serving. | `project`, `feature_name` | +| `feast_serving_not_found_feature_count` | No. of requests retrieving a Feature has resulted in a [`NOT_FOUND` field status.](../user-guide/feature-retrieval.md#online-field-statuses) | `project`, `feature_name` | +| `feast_serving_stale_feature_count` | No. of requests retrieving a Feature resulted in a [`OUTSIDE_MAX_AGE` field status.](../user-guide/feature-retrieval.md#online-field-statuses) | `project`, `feature_name` | +| `feast_serving_grpc_request_count` | Total gRPC requests served. | `method` | + +**Metric Tags** + +Exported Feast Serving metrics may be filtered by the following tags/keys + +| Tag | Description | +| :--- | :--- | +| `method` | Name of the Method that the request is calling. \(ie `ListFeatureSets`\) | +| `status_code` | Status code returned as a result of handling the requests \(ie `OK`\). Can be used to find request failures. | +| `project` | Name of the project that the FeatureSet of the Feature retrieved belongs to. | +| `feature_name` | Name of the Feature being retrieved. | + +## Feast Ingestion Job + +Feast Ingestion computes both metrics an statistics on [data ingestion.](../user-guide/data-ingestion.md) Make sure you familar with data ingestion concepts before proceeding. + +{% hint style="info" %} +For documentation on Feature value statistics computed by the Ingestion Job see [Statistics](../user-guide/statistics.md#inflight-feature-statistics) +{% endhint %} + +**Metrics Namespace** + +Metrics are computed at two stages of the Feature Row's/Feature Value's life cycle when being processed by the Ingestion Job: + +* `Inflight`- Prior to writing data to stores, but after successful validation of data. +* `WriteToStoreSucess`- After a successful store write. + +Metrics processed by each staged will be tagged with `metrics_namespace` to the stage where the metric was computed. + +**Metrics Bucketing** + +Metrics with a `{BUCKET}` are computed at various histogram buckets. Suffix with the following to select the bucket to use: + +* `min` - minimum value. +* `max` - maximum value. +* `mean`- mean value. +* `percentile_90`- 90 percentile. +* `percentile_95`- 95 percentile. +* `percentile_99`- 99 percentile. + +**Exported Metrics** + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MetricDescriptionTags
feast_ingestion_feature_row_lag_ms_{BUCKET} + Lag time in milliseconds between succeeding ingested Feature Rows. +

feast_store, feast_project_name,feast_featureSet_name,ingestion_job_name,

+

metrics_namespace +

+
feast_ingestion_feature_value_lag_ms_{BUCKET} + Lag time in milliseconds between succeeding ingested values for each Feature. +

feast_store, feast_project_name,feast_featureSet_name,

+

feast_feature_name,

+

ingestion_job_name,

+

metrics_namespace +

+
feast_ingestion_feature_row_ingested_count + No. of Ingested Feature Rows +

feast_store, feast_project_name,feast_featureSet_name,ingestion_job_name,

+

metrics_namespace +

+
feast_ingestion_feature_value_missing_count + No. of times a ingested Feature values did not provide a value for the + Feature. +

feast_store, feast_project_name,feast_featureSet_name,

+

feast_feature_name,

+

ingestion_job_name,

+

metrics_namespace +

+
feast_ingestion_deadletter_row_count + No. of Feature Rows that that the Ingestion Job did not successfully write + to store.feast_store, feast_project_name,feast_featureSet_name,ingestion_job_name +
+ +**Metric Tags** + +Exported Feast Ingestion Job metrics may be filtered by the following tags/keys + +| Tag | Description | +| :--- | :--- | +| `feast_store` | Name of the target store the Ingestion Job is writing to. | +| `feast_project_name` | Name of the project that the ingested FeatureSet belongs to. | +| `feast_featureSet_name` | Name of the Feature Set being ingested. | +| `feast_feature_name` | Name of the Feature being ingested. | +| `ingestion_job_name` | Name of the Ingestion Job performing data ingestion. Typically this is set to the Id of the Ingestion Job. | +| `metrics_namespace` | Stage where metrics where computed. Either `Inflight` or `WriteToStoreSuccess` | + diff --git a/docs/user-guide/metrics.md b/docs/user-guide/metrics.md index 7ba3113428c..f0e7b8c89d2 100644 --- a/docs/user-guide/metrics.md +++ b/docs/user-guide/metrics.md @@ -2,47 +2,56 @@ ## 0. Overview -Feast Components \(Core, Serving, Job Controller, Ingestion Jobs\) export metrics that can provide visibility into Feast usage and behavior such as: +Feast Components export metrics that can provide insight into Feast behavior: -* No. of Feature Sets registered with Feast Core. -* Online Feature Request latency retrieving from Feast Serving. -* Lag time between Feature Rows ingested into Feast Ingestion Jobs. +* [Feast Ingestion Jobs can be configured to push metrics into StatsD](metrics.md#2-exporting-feast-metrics-to-prometheus) +* [Prometheus can be configured to scrap metrics from Feast Core and Serving.](metrics.md#2-exporting-feast-metrics-to-prometheus) -## 1. Exporting Metrics to StatsD +See the [Metrics Reference ](../reference/metrics-reference.md)for documentation on metrics are exported by Feast. -Feast directly supports exporting metrics to a StatsD instance. +{% hint style="info" %} +Feast Job Controller currently does not export any metrics on its own. However its `application.yml` is used to configure metrics export for ingestion jobs. +{% endhint %} -* Metrics export to StatsD for Core, Serving and Job Controller components are configured in the component's coresponding`application.yml` +## 1. Pushing Ingestion Metrics to StatsD -```yaml -management: - metrics: - export: - statsd: - # Enables Statd metrics export if true. - enabled: true - # Host and port of the StatsD instance to export to. - host: localhost - port: 8125 -``` +**Feast Ingestion Job** -* Metrics export for Ingestion for the Ingestion Job component is configured in Job Controller's `application.yml` under `feast.jobs.metrics` +Feast Ingestion Job can be configured to push Ingestion metrics to a StatsD instance. Metrics export to StatsD for Ingestion Job is configured in Job Controller's `application.yml` under `feast.jobs.metrics`: ```yaml feast: jobs: metrics: # Enables Statd metrics export if true. - enabled: false - # Type of metrics sink. Only statsd is currently supported. + enabled: true type: statsd - # Host of the metrics sink. + # Host and port of the StatsD instance to export to. host: localhost - # Port of the metrics sink. port: 9125 ``` +{% hint style="info" %} +If you need Ingestion Metrics in Prometheus or some other metrics backend, use a metrics forwarder to forward Ingestion Metrics from StatsD to the metrics backend of choice. \(ie Use [`prometheus-statsd-exporter`](https://github.com/prometheus/statsd_exporter) to forward metrics to Prometheus\). +{% endhint %} + +## 2. Exporting Feast Metrics to Prometheus + +**Feast Core and Serving** + +Feast Core and Serving exports metrics to a Prometheus instance via Prometheus scraping its `/metrics` endpoint. Metrics export to Prometheus for Core and Serving can be configured via their corresponding `application.yml` + +```yaml +server: + # Configures the port where metrics are exposed via /metrics for Prometheus to scrape. + port: 8081 +``` + +[Direct Prometheus](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config) to scrape directly from Core and Serving's `/metrics` endpoint. +## 3. Futher Reading. +See the [Metrics Reference ](../reference/metrics-reference.md)for documentation on metrics are exported by Feast. +## diff --git a/docs/user-guide/statistics.md b/docs/user-guide/statistics.md index 89a15764fe1..7eae7eea20f 100644 --- a/docs/user-guide/statistics.md +++ b/docs/user-guide/statistics.md @@ -131,8 +131,8 @@ For insight into data currently flowing into Feast through the population jobs, Inflight feature statistics are windowed \(default window length is 30s\) and computed at two points in the feature population pipeline: -1. Prior to store writes, after successful validation -2. After successful store writes +1. Prior to store writes, after successful validation. +2. After successful store writes. The following metrics are written at the end of each window as [statsd gauges](https://github.com/statsd/statsd/blob/master/docs/metric_types.md#gauges): @@ -151,14 +151,14 @@ feast_ingestion_feature_value_percentile_99 the gauge metric type is used over histogram because statsd only supports positive values for histogram metric types, while numerical feature values can be of any double value. {% endhint %} -The metrics are tagged with and can be aggregated by the following keys: +The statistics are tagged with and can be aggregated by the following keys: -| key | description | +| Tag | Description | | :--- | :--- | -| feast\_store | store the population job is writing to | -| feast\_project\_name | feast project name | -| feast\_featureSet\_name | feature set name | -| feast\_feature\_name | feature name | -| ingestion\_job\_name | id of the population job writing the feature values. | -| metrics\_namespace | either `Inflight` or `WriteToStoreSuccess` | +| `feast_store` | Name of the target store the Ingestion Job is writing to. | +| `feast_project_name` | Name of the project that the ingested FeatureSet belongs to. | +| `feast_featureSet_name` | Name of the Feature Set being ingested. | +| `feast_feature_name` | Name of the Feature being ingested. | +| `ingestion_job_name` | Name of the Ingestion Job performing data ingestion. Typically this is set to the Id of the Ingestion Job. | +| `metrics_namespace` | Stage where metrics where computed. Either `Inflight` or `WriteToStoreSuccess` | From 9ca4c585b311e86632a606575ea82a8f91138d63 Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Wed, 16 Sep 2020 04:19:18 +0000 Subject: [PATCH 10/36] GitBook: [master] 2 pages and 4 assets modified --- docs/.gitbook/assets/rsz_untitled23.jpg | Bin 0 -> 23499 bytes docs/.gitbook/assets/untitled-23-.jpg | Bin 0 -> 80639 bytes docs/.gitbook/assets/untitled-25-1-.jpg | Bin 0 -> 76205 bytes docs/.gitbook/assets/untitled-26-1-.jpg | Bin 0 -> 81902 bytes docs/SUMMARY.md | 1 + docs/administration/security.md | 477 ++++++++++++++++++++++++ 6 files changed, 478 insertions(+) create mode 100644 docs/.gitbook/assets/rsz_untitled23.jpg create mode 100644 docs/.gitbook/assets/untitled-23-.jpg create mode 100644 docs/.gitbook/assets/untitled-25-1-.jpg create mode 100644 docs/.gitbook/assets/untitled-26-1-.jpg create mode 100644 docs/administration/security.md diff --git a/docs/.gitbook/assets/rsz_untitled23.jpg b/docs/.gitbook/assets/rsz_untitled23.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b92ec6fed7217bfcb91aca108b89dde598f4257d GIT binary patch literal 23499 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 C1c!nE literal 0 HcmV?d00001 diff --git a/docs/.gitbook/assets/untitled-23-.jpg b/docs/.gitbook/assets/untitled-23-.jpg new file mode 100644 index 0000000000000000000000000000000000000000..31299d475f23250c963d8f3bfc4e9ee75009d5da GIT binary patch literal 80639 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 literal 0 HcmV?d00001 diff --git a/docs/.gitbook/assets/untitled-25-1-.jpg b/docs/.gitbook/assets/untitled-25-1-.jpg new file mode 100644 index 0000000000000000000000000000000000000000..93d010406bd7181886ff0343b660d35216d73254 GIT binary patch literal 76205 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�{UZI7~IpNU^!Xls0#C##Ydby83(Y%4@Vv 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 aBJj7rYO()&;D1Npe@EbdU<4k1WB(sPKF}%v literal 0 HcmV?d00001 diff --git a/docs/.gitbook/assets/untitled-26-1-.jpg b/docs/.gitbook/assets/untitled-26-1-.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c4bbe15cf403284045bbf716c255fef787dc3998 GIT binary patch literal 81902 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 >W*2Aja$W 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 ZZHhnm{lB5s|4$JAQv!ce0yn>p{x6{u4O;*J literal 0 HcmV?d00001 diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 7f190173a9f..7415abbe4b2 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -35,6 +35,7 @@ ## Administration +* [Security](administration/security.md) * [Audit Logging](administration/audit-logging.md) * [Troubleshooting](administration/troubleshooting.md) * [Upgrading Feast](administration/upgrading.md) diff --git a/docs/administration/security.md b/docs/administration/security.md new file mode 100644 index 00000000000..6a4d336d665 --- /dev/null +++ b/docs/administration/security.md @@ -0,0 +1,477 @@ +--- +description: 'Secure Feast with SSL/TLS, Authentication and Authorization.' +--- + +# Security + +## 1. Overview + +![Overview of Feast's Security Methods.](../.gitbook/assets/untitled-25-1-.jpg) + +Feast supports the following security methods: + +* [SSL/TLS on messaging between Feast Core, Feast Serving and Feast SDKs.](security.md#2-ssl-tls) +* [Authentication to Feast Core and Serving based on Open ID Connect ID tokens.](security.md#3-authentication) +* [Authorization based on project membership and delegating authorization grants to external Authorization Server.](security.md#4-authorization) + +[Important notes to take note when using Authentication/Authorization](security.md#5-authentication-and-authorization). + +## **2. SSL/TLS** + +Feast supports SSL/TLS encryption for inter-service communication between Core, Serving and SDKs to be encrypted with SSL/TLS. + +### Configuring SSL/TLS on Core/Serving + +SSL/TLS can be configured via the following properties in their corresponding `application.yml`files: + +| Configuration {Property | Description | +| :--- | :--- | +| `grpc.server.security.enabled` | Enables SSL/TLS functionality if `true` | +| `grpc.server.security.certificateChain` | Provide the path to certificate chain. | +| `grpc.server.security.privateKey` | Provide the to private key. | + +> Read more on enabling SSL/TLS in the[ gRPC starter docs.](https://yidongnan.github.io/grpc-spring-boot-starter/en/server/security.html#enable-transport-layer-security) + +### Configuring SSL/TLS on Python SDK/CLI + +To enable SSL/TLS in the [Feast Python SDK](https://api.docs.feast.dev/python/#feast.client.Client)/CLI, the config options should be set via `feast config`: + +| Configuration Option | Description | +| :--- | :--- | +| `core_enable_ssl` | Enables SSL/TLS functionality on connections to Feast core if `true` | +| `serving_enable_ssl` | Enables SSL/TLS functionality on connections to Feast Serving if `true` | +| `core_server_ssl_cert` | Optional. Specifies the path of the root certificate used to verify Core Service's identity. If omitted, will use system certificates. | +| `serving_server_ssl_cert` | Optional. Specifies the path of the root certificate used to verify Serving Service's identity. If omitted, will use system certificates. | + +{% hint style="info" %} +The Python SDK automatically uses SSL/TLS when connecting to Feast Core/Serving via port 443. +{% endhint %} + +### Configuring SSL/TLS on Go SDK + +Configure SSL/TLS on the Go SDK by passing configuration via `SecurityConfig`: + +```go +cli, err := feast.NewSecureGrpcClient("localhost", 6566, feast.SecurityConfig{ + EnableTLS: true, + TLSCertPath: "/path/to/cert.pem", +})Option +``` + +| Config Option | Description | +| :--- | :--- | +| `EnableTLS` | Enables SSL/TLS functionality when connecting to Feast if `true` | +| `TLSCertPath` | Optional. Provides the path of the root certificate used to verify Feast Service's identity. If omitted, will use system certificates. | + +### Configuring SSL/TLS on **Java** SDK + +Configure SSL/TLS on the Java SDK by passing configuration via `SecurityConfig`: + +```java +FeastClient client = FeastClient.createSecure("localhost", 6566, + SecurityConfig.newBuilder() + .setTLSEnabled(true) + .setCertificatePath(Optional.of("/path/to/cert.pem")) + .build()); +``` + +| Config Option | Description | +| :--- | :--- | +| `setTLSEnabled()` | Enables SSL/TLS functionality when connecting to Feast if `true` | +| `setCertificatesPath()` | Optional. Set the path of the root certificate used to verify Feast Service's identity. If omitted, will use system certificates. | + +## **3. Authentication** + +{% hint style="warning" %} +It is recommended that SSL/TLS be enabled prior to enabling authentication to prevent man in the middle attacks. +{% endhint %} + +Authentication can be enabled to authenticate and identify client requests to Feast Core/Serving. Currently, Feast uses[ ](https://auth0.com/docs/protocols/openid-connect-protocol)[Open ID Connect \(OIDC\)](https://auth0.com/docs/protocols/openid-connect-protocol) ID tokens \(ie [Google Open ID Connect](https://developers.google.com/identity/protocols/oauth2/openid-connect)\) to authenticate client requests. + +#### Configuring Authentication in Core/Serving + +Authentication can be configured for Core/Serving via properties in their corresponding `application.yml`: + +| Configuration Property | Description | +| :--- | :--- | +| `feast.security.authentication.enabled` | Enables Authentication functionality if `true`. | +| `feast.security.authentication.provider` | Authentication Provider type. Currently only supports `jwt` | +| `feast.security.authentication.option.jwkEndpointURI` | HTTPS URL used by Feast to retrieved the [JWK](https://tools.ietf.org/html/rfc7517) used verify OIDC ID tokens. | + +{% hint style="info" %} +`jwkEndpointURI` is set retrieve Google's OIDC JWK by default, allowing OIDC ID tokens issued by Google to be used for authentication. +{% endhint %} + +Behind the scenes, Feast Core/Serving authenticates by: + +* Extracting the OIDC ID token `TOKEN`from gRPC metadata submitted with request: + +```text +('authorization', 'Bearer: TOKEN') +``` + +* Validating the token's signature using the JWK retrieved from the `jwkEndpointURI`to ensure token is authentic and produced by the Authentication Provider. + +### **Authenticating Serving with Core** + +Feast Serving needs to communicate with Core during normal operation. When both authentication and authorization is enabled on Core, Serving is forced to authenticate its requests to Core. Otherwise, Serving will fail to start with an Authentication failure error when connecting to Core. + + Properties used to configure Serving authentication via `application.yml`: + +| Configuration Property | Description | +| :--- | :--- | +| `feast.core-authentication.enabled` | Indicates to Serving to authenticate when communicating with Feast Core. | +| `feast.core-authentication.provider` | Selects the provider that Serving will use to retrieve credentials that it will use to authenticate with Core. Valid providers are `google` and `oauth`. | + +{% tabs %} +{% tab title="Google Provider" %} +Google Provider automatically extracts the credential from the credential JSON file. + +* [`GOOGLE_APPLICATION_CREDENTIALS` environment variable](https://cloud.google.com/docs/authentication/getting-started#setting_the_environment_variable) should be set to path of the credential JSON file. +{% endtab %} + +{% tab title="OAuth Provider" %} +OAuth Provider makes an OAuth [client credentials](https://auth0.com/docs/flows/call-your-api-using-the-client-credentials-flow) request to obtain the credential. It requires the following options to be set at `feast.security.core-authentication.options.`: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Configuration PropertyDescription
oauth_url + Target URL to make the client credentials request to.
grant_type + OAuth grant type. Should be set as client_credentials +
client_id + Client Id used in the client credentials request.
client_secret + Client secret used in the client credentials request.
audience + +

Target audience of the credential. Should be set to host URL of Core.

+

(ie https://localhost if Core listens on localhost).

+
jwkEndpointURI + HTTPS URL used to retrieve a JWK that can be used to decode the credential.
+{% endtab %} +{% endtabs %} + +### **Enabling Authentication in Python SDK/CLI** + +Configure the Feast Python SDK/CLI to use authentication via `feast config`: + +```python +$ feast config set enable_auth true +``` + +| Configuration Option | Description | +| :--- | :--- | +| `enable_auth` | Enables authentication functionality if set to `true`. | +| `auth_provider` | Use an authentication provider to obtain a credential for authentication. Currently supports `google` and `oauth`. | +| `auth_token` | Manually specify an static token for use in authentication. Overrules `auth_provider` if both are set. | + +{% tabs %} +{% tab title="Google Provider" %} +Google Provider automatically finds and use Google Credentials for authenticating requests: + +* Google Provider would automatically use user credentials for authenticating requests if user has authenticated with the `gcloud` CLI via: + +```text +$ gcloud auth application-default login +``` + +* Alternatively the Google Provider can be configured to use credentials JSON file via`GOOGLE_APPLICATION_CREDENTIALS` environmental variable \([read more](https://cloud.google.com/docs/authentication/getting-started)\): + +```bash +$ export GOOGLE_APPLICATION_CREDENTIALS="path/to/key.json" +``` +{% endtab %} + +{% tab title="OAuth Provider" %} +OAuth Provider makes an OAuth [client credentials](https://auth0.com/docs/flows/call-your-api-using-the-client-credentials-flow) request to obtain the credential/token used to authenticate Feast requests. The OAuth provider requires the following config options to be set via `feast config`: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Configuration PropertyDescription
oauth_token_request_url + Target URL to make the client credentials request to.
oauth_grant_type + OAuth grant type. Should be set as client_credentials +
oauth_client_id + Client Id used in the client credentials request.
oauth_client_secret + Client secret used in the client credentials request.
oauth_audience + +

Target audience of the credential. Should be set to host URL of target + Service.

+

(ie https://localhost if Service listens on localhost).

+
+{% endtab %} +{% endtabs %} + +### **Enabling Authentication in Go SDK** + +Configure the Feast Java SDK to use authentication by specifying credential via `SecurityConfig`: + +```go +// error handling omitted. +// Use Google Credential as provider. +cred, _ := feast.NewGoogleCredential("localhost:6566") +cli, _ := feast.NewSecureGrpcClient("localhost", 6566, feast.SecurityConfig{ + // Specify the credential to provide tokens for Feast Authentication. + Credential: cred, +}) +``` + +{% tabs %} +{% tab title="Google Credential" %} +Google Credential uses Service Account credentials JSON file set via`GOOGLE_APPLICATION_CREDENTIALS` environmental variable \([read more](https://cloud.google.com/docs/authentication/getting-started)\) to obtain tokens for Authenticating Feast requests: + +* Exporting `GOOGLE_APPLICATION_CREDENTIALS` + +```bash +$ export GOOGLE_APPLICATION_CREDENTIALS="path/to/key.json" +``` + +* Create Google Credential with target audience. + +```go +cred, _ := feast.NewGoogleCredential("localhost:6566") +``` + +> Target audience of the credential should be set to host URL of target Service. \(ie `https://localhost` if Service listens on `localhost`\): +{% endtab %} + +{% tab title="OAuth Credential" %} +OAuth Credential makes an OAuth [client credentials](https://auth0.com/docs/flows/call-your-api-using-the-client-credentials-flow) request to obtain the credential/token used to authenticate Feast requests: + +* Create OAuth Credential with parameters: + +```go +cred := feast.NewOAuthCredential("localhost:6566", "client_id", "secret", "https://oauth.endpoint/auth") +``` + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterDescription
audience + +

Target audience of the credential. Should be set to host URL of target + Service.

+

(ie https://localhost if Service listens on localhost).

+
clientId + Client Id used in the client credentials request.
clientSecret + Client secret used in the client credentials request.
endpointURL + Target URL to make the client credentials request to.
+{% endtab %} +{% endtabs %} + +### **Enabling Authentication in Java SDK** + +Configure the Feast Java SDK to use authentication by setting credentials via `SecurityConfig`: + +```java +// Use GoogleAuthCredential as provider. +CallCredentials credentials = new GoogleAuthCredentials( + Map.of("audience", "localhost:6566")); + +FeastClient client = FeastClient.createSecure("localhost", 6566, + SecurityConfig.newBuilder() + // Specify the credentials to provide tokens for Feast Authentication. + .setCredentials(Optional.of(creds)) + .build()); +``` + +{% tabs %} +{% tab title="GoogleAuthCredentials" %} +GoogleAuthCredentials uses Service Account credentials JSON file set via`GOOGLE_APPLICATION_CREDENTIALS` environmental variable \([read more](https://cloud.google.com/docs/authentication/getting-started)\) to obtain tokens for Authenticating Feast requests: + +* Exporting `GOOGLE_APPLICATION_CREDENTIALS` + +```bash +$ export GOOGLE_APPLICATION_CREDENTIALS="path/to/key.json" +``` + +* Create Google Credential with target audience. + +```java +CallCredentials credentials = new GoogleAuthCredentials( + Map.of("audience", "localhost:6566")); +``` + +> Target audience of the credentials should be set to host URL of target Service. \(ie `https://localhost` if Service listens on `localhost`\): +{% endtab %} + +{% tab title="OAuthCredentials" %} +OAuthCredentials makes an OAuth [client credentials](https://auth0.com/docs/flows/call-your-api-using-the-client-credentials-flow) request to obtain the credential/token used to authenticate Feast requests: + +* Create OAuthCredentials with parameters: + +```java +CallCredentials credentials = new OAuthCredentials(Map.of( + "audience": "localhost:6566", + "grant_type", "client_credentials", + "client_id", "some_id", + "client_id", "secret", + "oauth_url", "https://oauth.endpoint/auth", + "jwkEndpointURI", "https://jwk.endpoint/jwk")); +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterDescription
audience + +

Target audience of the credential. Should be set to host URL of target + Service.

+

(ie https://localhost if Service listens on localhost).

+
grant_type + OAuth grant type. Should be set as client_credentials +
client_id + Client Id used in the client credentials request.
client_secret + Client secret used in the client credentials request.
oauth_url + Target URL to make the client credentials request to obtain credential.
jwkEndpointURI + HTTPS URL used to retrieve a JWK that can be used to decode the credential.
+{% endtab %} +{% endtabs %} + +## 4. Authorization + +{% hint style="info" %} +Authorization requires authentication to be configured in order to obtain user identity to use for authorizing requests. +{% endhint %} + +Authorization provides access control to FeatureSets/Features based on project membership. Users that are members of a project are authorized to: + +* Create/Update a Feature Set in the Project. +* Retrieve Feature Values for Features in that Project. + +### **Authorization API/Server** + +![Feast Authorization Flow](../.gitbook/assets/rsz_untitled23.jpg) + +Feast delegates Authorization grants to a external Authorization Server that implements the [Authorization Open API specification](https://github.com/feast-dev/feast/blob/master/auth/src/main/resources/api.yaml). + +* Feast checks whether a user is authorized to make a request by making a `checkAccessRequest` to the Authorization Server. +* The Authorization Server should return a `AuthorizationResult` with whether user is allowed to make the request. + +Authorization can be configured for Core/Serving via properties in their corresponding `application.yml` + +| Configuration Property | Description | +| :--- | :--- | +| `feast.security.authorization.enabled` | Enables authorization functionality if `true`. | +| `feast.security.authorization.provider` | Authentication Provider type. Currently only supports `http` | +| `feast.security.authorization.option.authorizationUrl` | URL endpoint of Authorization Server to make check access requests to. | +| `feast.security.authorization.option.subjectClaim` | Optional. Name of the claim of the to extract from the ID Token to include in the check access request as Subject. | + +{% hint style="info" %} +Example of [Authorization Server with Keto](https://github.com/feast-dev/feast-keto-auth-server) can used as a reference implementation for implementing an Authorization Server that Feast supports. +{% endhint %} + +## **5. Authentication & Authorization** + +Things to note when using Authentication & Authorization: + +* Enabling Authentication without Authorization makes authentication **optional**. Users can still make requests unauthenticated. +* Enabling Authorization forces all requests made to be authenticated. Requests that are not authenticated are **dropped.** + From 3ec1cc288f12a212f6b44e1bd7eb508c4c7cfa40 Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Wed, 16 Sep 2020 04:19:38 +0000 Subject: [PATCH 11/36] GitBook: [master] 3 pages modified --- docs/SUMMARY.md | 1 + docs/contributing/release-process.md | 51 +++++-- .../api-supported-versions-and-deprecation.md | 137 ++++++++++++++++++ 3 files changed, 175 insertions(+), 14 deletions(-) create mode 100644 docs/reference/api-supported-versions-and-deprecation.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 7415abbe4b2..f0e2392f458 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -44,6 +44,7 @@ * [Metrics Reference](reference/metrics-reference.md) * [Configuration Reference](reference/configuration-reference.md) +* [API, Supported Versions & Deprecation](reference/api-supported-versions-and-deprecation.md) * [API Reference](reference/api/README.md) * [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) diff --git a/docs/contributing/release-process.md b/docs/contributing/release-process.md index a721e3b5ccf..a44a174dc74 100644 --- a/docs/contributing/release-process.md +++ b/docs/contributing/release-process.md @@ -8,7 +8,8 @@ Contributors are encouraged to understand our branch workflow described below, f * 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`. +* From the release branch, pre-release release candidates are tagged ie `v0.3.0-rc.1` +* From the release candidates, stable 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\). @@ -18,22 +19,37 @@ In general, unless you're committing code that only applies to a particular rele 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. -2. Update the [CHANGELOG.md](https://github.com/feast-dev/feast/blob/master/CHANGELOG.md). See the [Creating a change log](release-process.md#creating-a-change-log) guide. -3. In the root `pom.xml`, remove `-SNAPSHOT` from the `` property, and commit. -4. Push. For a new release branch, open a PR against master. -5. When CI passes, merge. \(Remember _not_ to delete the new release branch\). -6. Tag the merge commit with the release version, using a `v` and `sdk/go/v` prefixes \(ie for version `X.Y.Z` create tags `vX.Y.Z` and `sdk/go/vX.Y.Z`\). Push the tags. -7. Bump to the next working version and append `-SNAPSHOT` in `pom.xml`. -8. Commit the POM and open a PR. -9. 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](https://github.com/feast-dev/feast/blob/master/CHANGELOG.md). Use `Feast vX.Y.Z` as the title. -10. Create one final PR to the master branch and also update its [CHANGELOG.md](https://github.com/feast-dev/feast/blob/master/CHANGELOG.md). +1. For new 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. +2. Update the [CHANGELOG.md](https://github.com/feast-dev/feast/blob/master/CHANGELOG.md). See the [Creating a change log](release-process.md#creating-a-change-log) guide and commit + * Make to review each PR in the changelog to [flag any breaking changes and deprecation.](release-process.md#flag-breaking-changes-and-deprecations) +3. Update versions for the release/release candidate with a commit: + 1. In the root `pom.xml`, remove `-SNAPSHOT` from the `` property, update versions, and commit. + 2. Tag the commit with the release version, using a `v` and `sdk/go/v` prefixes + * for a release candidate, create tags `vX.Y.Z-rc.N`and `sdk/go/vX.Y.Z-rc.N` + * for a stable release `X.Y.Z` create tags `vX.Y.Z` and `sdk/go/vX.Y.Z` + 3. Check that versions are updated with `make lint-versions`. + 4. If changes required are flagged by the version lint, make the changes, amend the commit and move the tag to the new commit. +4. Push the commits and tags. Make sure the CI passes. + * If the CI does not pass, or if there are new patches for the release fix, repeat step 2 & 3 with release candidates until stable release is achieved. +5. Bump to the next patch version in the release branch, append `-SNAPSHOT` in `pom.xml` and push. +6. Create a PR against master to: + 1. Bump to the next major/minor version and append `-SNAPSHOT` . + 2. Add the change log by applying the change log commit created in step 2. + 3. Check that versions are updated with `env TARGET_MERGE_BRANCH=master make lint-versions` +7. Create a [GitHub release](https://github.com/feast-dev/feast/releases) which includes a summary of im~~p~~ortant changes as well as any artifacts associated with the release. Make sure to include the same change log as added in [CHANGELOG.md](https://github.com/feast-dev/feast/blob/master/CHANGELOG.md). Use `Feast vX.Y.Z` as the title. +8. Update the[ Upgrade Guide](../administration/upgrading.md) to include the action required instructions for users to upgrade to this new release. Instructions should include a migration for each breaking change made to this release. +9. Update[ Feast Supported Versions](../reference/api-supported-versions-and-deprecation.md#3-supported-versions) to include the supported versions of each component. 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. The `sdk/go/v tag` is required to version the Go SDK go module so that users can go get a specific tagged release of the Go SDK. ### 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. +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 change log 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. ```text docker run -it --rm ferrarimarco/github-changelog-generator \ @@ -53,7 +69,14 @@ docker run -it --rm ferrarimarco/github-changelog-generator \ 1. 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 each item is categorised correctly. You will see the following categories: `Breaking changes`, `Implemented enhancements`, `Fixed bugs`, and `Merged pull requests`. Any unlabelled 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 Change log" 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. +### Flag Breaking Changes & Deprecations + +It's important to flag breaking changes and deprecation to the API for each release so that we can maintain [API compatibility](../reference/api-supported-versions-and-deprecation.md#2-api-compatibility). + +* Developers should have flagged PRs with breaking changes with the `compat/breaking` label. However, it's important to double check each PR's release notes and contents for changes that will break [API compatibility](https://app.gitbook.com/@feast/s/docs/~/drafts/-MGstXoieRglzkPw_d1Q/v/master/reference/api-supported-versions-and-deprecation#2-api-compatibility) and manually label `compat/breaking` to PRs with undeclared breaking changes. The change log will have to be regenerated if any new labels have to be added. +* Record any deprecation in [Deprecations Page](../reference/api-supported-versions-and-deprecation.md#4-deprecations). + diff --git a/docs/reference/api-supported-versions-and-deprecation.md b/docs/reference/api-supported-versions-and-deprecation.md new file mode 100644 index 00000000000..8a9f8bba9be --- /dev/null +++ b/docs/reference/api-supported-versions-and-deprecation.md @@ -0,0 +1,137 @@ +--- +description: 'Tracking Feast''s API Compatibility, support Feast versions, Deprecation.' +--- + +# API, Supported Versions & Deprecation + +## Overview + +This document tracks Feast's API compatibility. Users are dependent on Feast providing a stable, compatible API such that Feast upgrades do not significantly impact the systems that they have built on Feast, while developers have to make changes to correct API design decisions that no longer make sense. By tracking Feast's API Compatibility, support Feast versions, this document attempts to find common ground between the two needs. + +1. [Release Versioning](api-supported-versions-and-deprecation.md#1-release-versioning) +2. [API Compatibility Policy](api-supported-versions-and-deprecation.md#2-api-compatibility) +3. [Supported Feast Versions.](api-supported-versions-and-deprecation.md#3-supported-versions) +4. [Deprecations.](api-supported-versions-and-deprecation.md#4-deprecations) + +## 1. Release Versioning + +Feast follows [semantic versioning ](https://semver.org/)to version its components in the form `MAJOR.MINOR.PATCH`. Different Feast Components are versioned together \(ie Core, Serving, SDKs\). + +* Stable releases are versioned `MAJOR.MINOR.PATCH` +* Pre-release releases are versioned as `MAJOR.MINOR.PATCH-SNAPSHOT` or `MAJOR.MINOR.PATCH-rc.NUMBER` for release candidates. + +{% hint style="danger" %} +While Feast is still pre-1.0 breaking changes will happen in minor versions. +{% endhint %} + +## 2. API Compatibility + +### Defining API + +Feast defines API as anything that is directly user facing, accessible by the user directly. This includes: + +* SDKs' user facing methods/classes. +* Protobufs used in Feast messaging. +* gRPC/HTTP Service APIs. +* Database schemas. +* Configuration Files. + +### Breaking Changes + +Defining Breaking Changes: + +* Adding something that users need to use for existing functionality \(eg. adding a required parameter\). +* Changing how a given API should be interpreted. +* Changing a default value in parameter in the API. +* Removing something from the API. + +### Introducing Breaking Changes + +What steps should developers take when proposing breaking changes. + +* Propose a deprecation by making a Pull Request and updating the `Deprecation` section in this document. +* Flag deprecated APIs with `deprecated` in code and in release notes in the Pull Request. + +Once deprecation period has expired the developer may introduce the breaking change: + +* Flag Breaking changes in release notes with `breaking` and `action required`. +* Ensure that **detailed upgrade instructions** are provided in the release notes for users to follow when upgrading. + +## 3. Supported Versions + +### Support Table + +Support Table defines which versions of Feast components are currently supported: + +| Component | Supported Versions | +| :--- | :--- | +| Feast Core | 0.7 | +| Feast Serving | 0.7 | +| Feast Go SDK | 0.6, 0.7 | +| Feast Python SDK/CLI | 0.6, 0.7 | +| Feast Java SDK | 0.6, 0.7 | + +### Patch Releases + +Patch Releases typically contain critical backwards compatible bug fixes. Users should ensure that they are running the latest patch release of the selected minor version of Feast. + +### **Component Skew** + +Feast's toleration for Component Skew is as follows: + +* Feast Core, Serving, Job Coordinator are compatible with the same patch version. +* Feast Core/Serving/Job Coordinator are backwards compatible with Feast SDKs for one minor version. \(ie Feast 0.6 is compatible with Feast 0.5 SDKs\). + +## 4. Deprecations + +Tracks deprecation in Feast APIs, expiry release and mitigation and migration. + +{% hint style="warning" %} +A breaking change or removal can be made to a deprecated API when it reaches its expiry release. Users are encouraged to migrate their systems before the expiry release. +{% endhint %} + + + + + + + + + + + + + + + + + + + + + + + + +
DeprecationBreaking Change ReleaseComponents AffectedMitigation/Migration
Specifying project in FeatureReferences + when retrieving from Serving via GetOnlineFeatures is no longer + supported.0.8Serving's Service API +
    +
  • SDK users: Migrate to a SDK with version >= 0.6
  • +
  • API users: Use specialized project field + in GetOnlineFeaturesRequest when retrieving via GetOnlineFeatures.
  • +
+
+
    +
  • get_batch_features will be changed to get_historical_features +
  • +
  • get_online_features entity_rows field will only take in a + list of dictionaries instead ofGetOnlineFeaturesRequest.EntityRow +
  • +
+
0.8Python SDK +
    +
  • Python SDK users: Migrate to a SDK with version >= 0.7
  • +
+
+ From 9dd446bdcd318c81674b312bb8df4f4ba7fbdf46 Mon Sep 17 00:00:00 2001 From: Oleksii Moskalenko Date: Wed, 16 Sep 2020 13:09:02 +0800 Subject: [PATCH 12/36] call fallback only when theres missing keys (#1009) --- .../redis/retriever/RedisClusterOnlineRetriever.java | 4 ++++ .../redis/retriever/RedisClusterOnlineRetrieverTest.java | 9 +++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/retriever/RedisClusterOnlineRetriever.java b/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/retriever/RedisClusterOnlineRetriever.java index 8170adc9d2b..c49745bbbd1 100644 --- a/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/retriever/RedisClusterOnlineRetriever.java +++ b/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/retriever/RedisClusterOnlineRetriever.java @@ -228,6 +228,10 @@ private List sendMultiGet(List keys) { .boxed() .collect(Collectors.toList()); + if (indexMissingValue.isEmpty()) { + return redisValues; + } + byte[][] fallbackBinaryKeys = indexMissingValue.stream() .map(i -> fallbackSerializer.serialize(keys.get(i))) diff --git a/storage/connectors/redis/src/test/java/feast/storage/connectors/redis/retriever/RedisClusterOnlineRetrieverTest.java b/storage/connectors/redis/src/test/java/feast/storage/connectors/redis/retriever/RedisClusterOnlineRetrieverTest.java index fc042cd9240..419ce8e0a95 100644 --- a/storage/connectors/redis/src/test/java/feast/storage/connectors/redis/retriever/RedisClusterOnlineRetrieverTest.java +++ b/storage/connectors/redis/src/test/java/feast/storage/connectors/redis/retriever/RedisClusterOnlineRetrieverTest.java @@ -18,7 +18,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; import static org.mockito.MockitoAnnotations.initMocks; import com.google.common.collect.ImmutableList; @@ -147,7 +147,9 @@ public void shouldReturnResponseWithValuesIfKeysPresent() { List> featureRowBytes = Lists.newArrayList(keyValue1, keyValue2); OnlineRetriever redisClusterOnlineRetriever = - new RedisClusterOnlineRetriever.Builder(connection, serializer).build(); + new RedisClusterOnlineRetriever.Builder(connection, serializer) + .withFallbackSerializer(fallbackSerializer) + .build(); when(syncCommands.mget(serializedKey1, serializedKey2)).thenReturn(featureRowBytes); List> expected = @@ -174,6 +176,9 @@ public void shouldReturnResponseWithValuesIfKeysPresent() { List> actual = redisClusterOnlineRetriever.getOnlineFeatures(entityRows, featureSetRequest); assertThat(actual, equalTo(expected)); + + // check that fallback is used only when there's something to fallback + verify(syncCommands, never()).mget(); } @Test From 046bf6f2aa2c910badb9f495e465767584d59465 Mon Sep 17 00:00:00 2001 From: Oleksii Moskalenko Date: Wed, 16 Sep 2020 14:18:02 +0800 Subject: [PATCH 13/36] wait for FeatureSet ready, then timeout (#1010) --- infra/scripts/test-end-to-end-batch-dataflow.sh | 2 +- tests/e2e/bq/bq-batch-retrieval.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/infra/scripts/test-end-to-end-batch-dataflow.sh b/infra/scripts/test-end-to-end-batch-dataflow.sh index 550f06fc583..363ba7dc471 100755 --- a/infra/scripts/test-end-to-end-batch-dataflow.sh +++ b/infra/scripts/test-end-to-end-batch-dataflow.sh @@ -123,7 +123,7 @@ Helm install common parts (kafka, redis, etc) " cd $ORIGINAL_DIR/infra/charts/feast - helm install --wait --debug --values="values-end-to-end-batch-dataflow-updated.yaml" \ + helm install --replace --wait --debug --values="values-end-to-end-batch-dataflow-updated.yaml" \ --set "feast-core.enabled=false" \ --set "feast-online-serving.enabled=false" \ --set "feast-batch-serving.enabled=false" \ diff --git a/tests/e2e/bq/bq-batch-retrieval.py b/tests/e2e/bq/bq-batch-retrieval.py index b7727a0db02..2d94d2e6cf4 100644 --- a/tests/e2e/bq/bq-batch-retrieval.py +++ b/tests/e2e/bq/bq-batch-retrieval.py @@ -20,11 +20,13 @@ from feast.client import Client from feast.contrib.job_controller.client import Client as JCClient from feast.core.CoreService_pb2 import ListStoresRequest +from feast.core.FeatureSet_pb2 import FeatureSetStatus 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 feast.wait import wait_retry_backoff pd.set_option("display.max_columns", None) @@ -181,6 +183,17 @@ def test_batch_get_historical_features_with_file(client): # feature set may be ready (direct runner set ready right after job submitted), # but kafka consumer is not configured # give some time to warm up ingestion job + wait_retry_backoff( + retry_fn=( + lambda: ( + None, + client.get_feature_set(name="file_feature_set").status + == FeatureSetStatus.STATUS_READY, + ) + ), + timeout_secs=480, + timeout_msg="Wait for FeatureSet to be READY", + ) time.sleep(20) client.ingest(file_fs1, features_1_df, timeout=480) From 6014e0a1857ccb8b079eec2ce1cc8345b80af622 Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Wed, 16 Sep 2020 07:42:56 +0000 Subject: [PATCH 14/36] GitBook: [master] 37 pages modified --- docs/roadmap.md | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/docs/roadmap.md b/docs/roadmap.md index 0867adddf5b..6272227a5db 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,6 +1,6 @@ # Roadmap -## Feast 0.7 \(Feature Release\) +## Feast 0.7 [Discussion](https://github.com/feast-dev/feast/issues/834) @@ -8,25 +8,20 @@ ### **New Functionality** -1. Entities as a first-class concept [\#405](https://github.com/feast-dev/feast/issues/405) -2. ~~Datasets as a first-class concept~~ \(pushed out\) -3. Feast UI \(MVP\) -4. Native SDK types instead of proto types -5. Audit log support -6. Request/response logging for online serving +1. Label based Ingestion Job selector for Job Controller [\#903](https://github.com/feast-dev/feast/pull/903) +2. Authentication Support for Java & Go SDKs [\#971](https://github.com/feast-dev/feast/pull/971) +3. Automatically Restart Ingestion Jobs on Upgrade [\#949](https://github.com/feast-dev/feast/pull/949) +4. Structured Audit Logging [\#891](https://github.com/feast-dev/feast/pull/891) +5. Request Response Logging support via Fluentd [\#961](https://github.com/feast-dev/feast/pull/961) +6. Feast Core Rest Endpoints [\#878](https://github.com/feast-dev/feast/pull/878) ### **Technical debt, refactoring, or housekeeping** -1. Improved integration testing framework -2. Rectify all flaky batch tests -3. Decouple job management from Feast Core -4. More descriptive error logs in Feast online serving +1. Improved integration testing framework [\#886](https://github.com/feast-dev/feast/pull/886) +2. Rectify all flaky batch tests [\#953](https://github.com/feast-dev/feast/pull/953), [\#982](https://github.com/feast-dev/feast/pull/982) +3. Decouple job management from Feast Core [\#951](https://github.com/feast-dev/feast/pull/951) -### **Proposals** - -1. Training-serving skew detection proposal - -## Feast 0.6 \(Feature Release\) +## Feast 0.6 [Discussion](https://github.com/feast-dev/feast/issues/767) @@ -45,7 +40,7 @@ 1. Improved job life cycle management [\#761](https://github.com/feast-dev/feast/issues/761) 2. Compute and write metrics for rows prior to store writes [\#763](https://github.com/feast-dev/feast/pull/763) -## Feast 0.5 \(Technical Release\) +## Feast 0.5 [Discussion](https://github.com/gojek/feast/issues/527) From e35d215d64c5218a8947365c991f6111678a60a2 Mon Sep 17 00:00:00 2001 From: Terence Lim Date: Wed, 16 Sep 2020 16:29:02 +0800 Subject: [PATCH 15/36] Probe for available port (#1004) --- .../test/java/feast/core/auth/CoreServiceAuthenticationIT.java | 3 ++- .../test/java/feast/core/auth/CoreServiceAuthorizationIT.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/feast/core/auth/CoreServiceAuthenticationIT.java b/core/src/test/java/feast/core/auth/CoreServiceAuthenticationIT.java index 9ecea9a290d..d6f13fdb55d 100644 --- a/core/src/test/java/feast/core/auth/CoreServiceAuthenticationIT.java +++ b/core/src/test/java/feast/core/auth/CoreServiceAuthenticationIT.java @@ -41,6 +41,7 @@ import org.springframework.boot.test.context.TestConfiguration; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; +import org.springframework.util.SocketUtils; @SpringBootTest( properties = { @@ -52,7 +53,7 @@ public class CoreServiceAuthenticationIT extends BaseIT { @Autowired FeastProperties feastProperties; private static int feast_core_port; - private static int JWKS_PORT = 45124; + private static int JWKS_PORT = SocketUtils.findAvailableTcpPort(); private static JwtHelper jwtHelper = new JwtHelper(); diff --git a/core/src/test/java/feast/core/auth/CoreServiceAuthorizationIT.java b/core/src/test/java/feast/core/auth/CoreServiceAuthorizationIT.java index 0c430b0915d..584fcc3854a 100644 --- a/core/src/test/java/feast/core/auth/CoreServiceAuthorizationIT.java +++ b/core/src/test/java/feast/core/auth/CoreServiceAuthorizationIT.java @@ -51,6 +51,7 @@ import org.springframework.boot.test.context.TestConfiguration; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; +import org.springframework.util.SocketUtils; import org.testcontainers.containers.DockerComposeContainer; import sh.ory.keto.ApiClient; import sh.ory.keto.ApiException; @@ -73,7 +74,7 @@ public class CoreServiceAuthorizationIT extends BaseIT { private static int KETO_PORT = 4466; private static int KETO_ADAPTOR_PORT = 8080; private static int feast_core_port; - private static int JWKS_PORT = 45124; + private static int JWKS_PORT = SocketUtils.findAvailableTcpPort(); private static JwtHelper jwtHelper = new JwtHelper(); From 535a0d57da211ed190c8eb3dd79dbc093be5d71a Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Wed, 16 Sep 2020 09:59:36 +0000 Subject: [PATCH 16/36] GitBook: [master] 37 pages and 7 assets modified --- docs/.gitbook/assets/blank-diagram-4 (3).svg | 1 + .../feast-docs-overview-diagram-2 (5).svg | 1 + docs/.gitbook/assets/image (2) (3).png | Bin 0 -> 149255 bytes docs/.gitbook/assets/image (3) (2) (2).png | Bin 0 -> 17434 bytes docs/.gitbook/assets/rsz_untitled23 (1).jpg | Bin 0 -> 23499 bytes .../assets/statistics-sources (1) (2).png | Bin 0 -> 165507 bytes docs/.gitbook/assets/untitled-25-1- (1).jpg | Bin 0 -> 76205 bytes docs/reference/metrics-reference.md | 9 ++++++++- 8 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 docs/.gitbook/assets/blank-diagram-4 (3).svg create mode 100644 docs/.gitbook/assets/feast-docs-overview-diagram-2 (5).svg create mode 100644 docs/.gitbook/assets/image (2) (3).png create mode 100644 docs/.gitbook/assets/image (3) (2) (2).png create mode 100644 docs/.gitbook/assets/rsz_untitled23 (1).jpg create mode 100644 docs/.gitbook/assets/statistics-sources (1) (2).png create mode 100644 docs/.gitbook/assets/untitled-25-1- (1).jpg diff --git a/docs/.gitbook/assets/blank-diagram-4 (3).svg b/docs/.gitbook/assets/blank-diagram-4 (3).svg new file mode 100644 index 00000000000..fb5e0659e55 --- /dev/null +++ b/docs/.gitbook/assets/blank-diagram-4 (3).svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/.gitbook/assets/feast-docs-overview-diagram-2 (5).svg b/docs/.gitbook/assets/feast-docs-overview-diagram-2 (5).svg new file mode 100644 index 00000000000..7f30963ec78 --- /dev/null +++ b/docs/.gitbook/assets/feast-docs-overview-diagram-2 (5).svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/.gitbook/assets/image (2) (3).png b/docs/.gitbook/assets/image (2) (3).png new file mode 100644 index 0000000000000000000000000000000000000000..d3b359a5988f3b0d75afe44b0ae2cd7d9ef0aab1 GIT binary patch literal 149255 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 literal 0 HcmV?d00001 diff --git a/docs/.gitbook/assets/image (3) (2) (2).png b/docs/.gitbook/assets/image (3) (2) (2).png new file mode 100644 index 0000000000000000000000000000000000000000..2442410112fc9413bf4969dddf296cb06f201d3a GIT binary patch literal 17434 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 literal 0 HcmV?d00001 diff --git a/docs/.gitbook/assets/rsz_untitled23 (1).jpg b/docs/.gitbook/assets/rsz_untitled23 (1).jpg new file mode 100644 index 0000000000000000000000000000000000000000..b92ec6fed7217bfcb91aca108b89dde598f4257d GIT binary patch literal 23499 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 C1c!nE literal 0 HcmV?d00001 diff --git a/docs/.gitbook/assets/statistics-sources (1) (2).png b/docs/.gitbook/assets/statistics-sources (1) (2).png new file mode 100644 index 0000000000000000000000000000000000000000..02be233968d3c97e932f8073b66d41b49247782c GIT binary patch literal 165507 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�{UZI7~IpNU^!Xls0#C##Ydby83(Y%4@Vv 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 aBJj7rYO()&;D1Npe@EbdU<4k1WB(sPKF}%v literal 0 HcmV?d00001 diff --git a/docs/reference/metrics-reference.md b/docs/reference/metrics-reference.md index 67725a8a573..6203c5e249c 100644 --- a/docs/reference/metrics-reference.md +++ b/docs/reference/metrics-reference.md @@ -78,7 +78,7 @@ Metrics processed by each staged will be tagged with `metrics_namespace` to the **Metrics Bucketing** -Metrics with a `{BUCKET}` are computed at various histogram buckets. Suffix with the following to select the bucket to use: +Metrics with a `{BUCKET}` are computed on a 60 second window/bucket. Suffix with the following to select the bucket to use: * `min` - minimum value. * `max` - maximum value. @@ -122,6 +122,13 @@ Metrics with a `{BUCKET}` are computed at various histogram buckets. Suffix with

+ + feast_ingestion_feature_value_{BUCKET} + + Last value feature for each Feature. + feast_store, feature_project_name, feast_feature_name,feast_featureSet_name, ingest_job_name, metrics_namepace + + feast_ingestion_feature_row_ingested_count From b7779042cd78b0d5845af34f8cad97cd5c57ea8c Mon Sep 17 00:00:00 2001 From: Terence Lim Date: Thu, 17 Sep 2020 10:21:02 +0800 Subject: [PATCH 17/36] Remove and update deprecation functions (#996) --- .../tests/test-feast-batch-serving.yaml | 2 +- .../tests/test-feast-online-serving.yaml | 11 ++-- sdk/python/feast/client.py | 38 +------------ sdk/python/tests/test_client.py | 6 ++- tests/e2e/redis/basic-ingest-redis-serving.py | 53 ++++++------------- 5 files changed, 26 insertions(+), 84 deletions(-) diff --git a/infra/charts/feast/templates/tests/test-feast-batch-serving.yaml b/infra/charts/feast/templates/tests/test-feast-batch-serving.yaml index f2a76e37b21..1fa6b0cf2ee 100644 --- a/infra/charts/feast/templates/tests/test-feast-batch-serving.yaml +++ b/infra/charts/feast/templates/tests/test-feast-batch-serving.yaml @@ -15,7 +15,7 @@ spec: - bash - -c - | - pip install -U feast==0.4.* + pip install -U feast==0.7.* cat < featureset.yaml kind: feature_set diff --git a/infra/charts/feast/templates/tests/test-feast-online-serving.yaml b/infra/charts/feast/templates/tests/test-feast-online-serving.yaml index f9a213aabb8..74acb4d26d9 100644 --- a/infra/charts/feast/templates/tests/test-feast-online-serving.yaml +++ b/infra/charts/feast/templates/tests/test-feast-online-serving.yaml @@ -15,7 +15,7 @@ spec: - bash - -c - | - pip install -U feast==0.4.* + pip install -U feast==0.7.* cat < featureset.yaml kind: feature_set @@ -68,12 +68,9 @@ spec: time.sleep(5) entity_rows=[ - GetOnlineFeaturesRequest.EntityRow( - fields={"customer_id": Value(int64_val=0)}), - GetOnlineFeaturesRequest.EntityRow( - fields={"customer_id": Value(int64_val=1)}), - GetOnlineFeaturesRequest.EntityRow( - fields={"customer_id": Value(int64_val=2)}), + {"customer_id": Value(int64_val=0)}, + {"customer_id": Value(int64_val=1)}, + {"customer_id": Value(int64_val=2)}, ] result_df = client.get_online_features( diff --git a/sdk/python/feast/client.py b/sdk/python/feast/client.py index b4a36ae8952..8d49e95ed67 100644 --- a/sdk/python/feast/client.py +++ b/sdk/python/feast/client.py @@ -19,7 +19,6 @@ import tempfile import time import uuid -import warnings from collections import OrderedDict from math import ceil from typing import Any, Dict, List, Optional, Tuple, Union, cast @@ -91,8 +90,6 @@ CPU_COUNT: int = multiprocessing.cpu_count() -warnings.simplefilter("once", DeprecationWarning) - class Client: """ @@ -543,25 +540,6 @@ def list_entities(self) -> Dict[str, Entity]: entities_dict[entity.name] = entity return entities_dict - def get_batch_features( - self, - feature_refs: List[str], - entity_rows: Union[pd.DataFrame, str], - compute_statistics: bool = False, - project: str = None, - ) -> RetrievalJob: - """ - Deprecated. Please see get_historical_features. - """ - warnings.warn( - "The method get_batch_features() is being deprecated. Please use the identical get_historical_features(). " - "Feast 0.7 and onwards will not support get_batch_features().", - DeprecationWarning, - ) - return self.get_historical_features( - feature_refs, entity_rows, compute_statistics, project - ) - def get_historical_features( self, feature_refs: List[str], @@ -683,7 +661,7 @@ def get_historical_features( def get_online_features( self, feature_refs: List[str], - entity_rows: List[Union[GetOnlineFeaturesRequest.EntityRow, Dict[str, Any]]], + entity_rows: List[Dict[str, Any]], project: Optional[str] = None, omit_entities: bool = False, ) -> OnlineResponse: @@ -964,7 +942,7 @@ def _get_grpc_metadata(self): def _infer_online_entity_rows( - entity_rows: List[Union[GetOnlineFeaturesRequest.EntityRow, Dict[str, Any]]], + entity_rows: List[Dict[str, Any]], ) -> List[GetOnlineFeaturesRequest.EntityRow]: """ Builds a list of EntityRow protos from Python native type format passed by user. @@ -976,18 +954,6 @@ def _infer_online_entity_rows( Returns: A list of EntityRow protos parsed from args. """ - - # Maintain backward compatibility with users providing EntityRow Proto - if entity_rows and isinstance(entity_rows[0], GetOnlineFeaturesRequest.EntityRow): - warnings.warn( - "entity_rows parameter will only be accepting Dict format from Feast v0.7 onwards", - DeprecationWarning, - ) - entity_rows_proto = cast( - List[Union[GetOnlineFeaturesRequest.EntityRow]], entity_rows - ) - return entity_rows_proto - entity_rows_dicts = cast(List[Dict[str, Any]], entity_rows) entity_row_list = [] entity_type_map = dict() diff --git a/sdk/python/tests/test_client.py b/sdk/python/tests/test_client.py index 8d591d26b9e..b9ef22b95b0 100644 --- a/sdk/python/tests/test_client.py +++ b/sdk/python/tests/test_client.py @@ -342,12 +342,14 @@ def int_val(x): ] ) recieve_response = GetOnlineFeaturesResponse() + entity_rows = [] for row_number in range(1, ROW_COUNT + 1): request.entity_rows.append( GetOnlineFeaturesRequest.EntityRow( fields={"driver_id": int_val(row_number)} ) - ), + ) + entity_rows.append({"driver_id": int_val(row_number)}) field_values = GetOnlineFeaturesResponse.FieldValues( fields={ "driver_id": int_val(row_number), @@ -370,7 +372,7 @@ def int_val(x): return_value=recieve_response, ) got_response = mocked_client.get_online_features( - entity_rows=request.entity_rows, + entity_rows=entity_rows, feature_refs=["driver:age", "rating", "null_value"], project="driver_project", ) # type: GetOnlineFeaturesResponse diff --git a/tests/e2e/redis/basic-ingest-redis-serving.py b/tests/e2e/redis/basic-ingest-redis-serving.py index 1c115cba1ef..92134af6daa 100644 --- a/tests/e2e/redis/basic-ingest-redis-serving.py +++ b/tests/e2e/redis/basic-ingest-redis-serving.py @@ -26,10 +26,7 @@ from feast.feature import Feature from feast.feature_set import FeatureSet, FeatureSetRef from feast.grpc.auth import get_auth_metadata_plugin -from feast.serving.ServingService_pb2 import ( - GetOnlineFeaturesRequest, - GetOnlineFeaturesResponse, -) +from feast.serving.ServingService_pb2 import GetOnlineFeaturesResponse from feast.source import KafkaSource from feast.type_map import ValueType from feast.types.Value_pb2 import Int64List @@ -264,13 +261,7 @@ def test_basic_retrieve_online_success(client, cust_trans_df): def try_get_features(): response = client.get_online_features( entity_rows=[ - GetOnlineFeaturesRequest.EntityRow( - fields={ - "customer_id": Value( - int64_val=cust_trans_df.iloc[0]["customer_id"] - ) - } - ) + {"customer_id": Value(int64_val=cust_trans_df.iloc[0]["customer_id"])} ], feature_refs=feature_refs, ) # type: GetOnlineFeaturesResponse @@ -305,14 +296,12 @@ def try_get_features(): feature_refs = [mapping[0] for mapping in feature_ref_df_mapping] 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"]), - } - ) + { + "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=feature_refs, ) # type: GetOnlineFeaturesResponse @@ -986,13 +975,7 @@ def test_all_types_retrieve_online_success(client, all_types_dataframe): def try_get_features(): response = client.get_online_features( entity_rows=[ - GetOnlineFeaturesRequest.EntityRow( - fields={ - "user_id": Value( - int64_val=all_types_dataframe.iloc[0]["user_id"] - ) - } - ) + {"user_id": Value(int64_val=all_types_dataframe.iloc[0]["user_id"])} ], feature_refs=feature_refs, ) # type: GetOnlineFeaturesResponse @@ -1134,13 +1117,11 @@ def test_large_volume_retrieve_online_success(client, large_volume_dataframe): while True: response = client.get_online_features( entity_rows=[ - GetOnlineFeaturesRequest.EntityRow( - fields={ - "customer_id": Value( - int64_val=large_volume_dataframe.iloc[0]["customer_id"] - ) - } - ) + { + "customer_id": Value( + int64_val=large_volume_dataframe.iloc[0]["customer_id"] + ) + } ], feature_refs=feature_refs, ) # type: GetOnlineFeaturesResponse @@ -1432,11 +1413,7 @@ def test_sink_writes_only_recent_rows(client): def try_get_features(): response = client.get_online_features( - entity_rows=[ - GetOnlineFeaturesRequest.EntityRow( - fields={"driver_id": Value(int64_val=later_df.iloc[0]["driver_id"])} - ) - ], + entity_rows=[{"driver_id": Value(int64_val=later_df.iloc[0]["driver_id"])}], feature_refs=feature_refs, ) # type: GetOnlineFeaturesResponse is_ok = all( From 4b48253b045422428d92601ead642972671b1409 Mon Sep 17 00:00:00 2001 From: Terence Lim Date: Thu, 17 Sep 2020 10:48:02 +0800 Subject: [PATCH 18/36] Add validation for dataflow region (#1005) --- .../runner/dataflow/DataflowRunnerConfig.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/job-controller/src/main/java/feast/jobcontroller/runner/dataflow/DataflowRunnerConfig.java b/job-controller/src/main/java/feast/jobcontroller/runner/dataflow/DataflowRunnerConfig.java index 1e70d2592e5..7e348997287 100644 --- a/job-controller/src/main/java/feast/jobcontroller/runner/dataflow/DataflowRunnerConfig.java +++ b/job-controller/src/main/java/feast/jobcontroller/runner/dataflow/DataflowRunnerConfig.java @@ -16,6 +16,7 @@ */ package feast.jobcontroller.runner.dataflow; +import feast.common.validators.OneOfStrings; import feast.jobcontroller.runner.option.RunnerConfig; import feast.proto.core.RunnerProto.DataflowRunnerConfigOptions; import java.util.Map; @@ -55,7 +56,23 @@ public DataflowRunnerConfig(DataflowRunnerConfigOptions runnerConfigOptions) { @NotBlank public String project; /* The Google Compute Engine region for creating Dataflow jobs. */ - @NotBlank public String region; + @OneOfStrings({ + "us-west1", + "us-central1", + "us-east1", + "us-east4", + "northamerica-northeast1", + "europe-west1", + "europe-west2", + "europe-west3", + "europe-west4", + "asia-southeast1", + "asia-east1", + "asia-northeast1", + "australia-southeast1" + }) + @NotBlank + public String region; /* GCP availability zone for operations. */ @NotBlank public String workerZone; From 413c4f1d6de48c890b83f55289d5a30e95cfb914 Mon Sep 17 00:00:00 2001 From: Terence Lim Date: Thu, 17 Sep 2020 11:03:02 +0800 Subject: [PATCH 19/36] Fix py sdk historical retrieval conversion (#1002) --- sdk/python/feast/job.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/sdk/python/feast/job.py b/sdk/python/feast/job.py index 2a45207cbff..ff684d9cbed 100644 --- a/sdk/python/feast/job.py +++ b/sdk/python/feast/job.py @@ -134,13 +134,9 @@ def to_dataframe( ) -> pd.DataFrame: """ Wait until a job is done to get an iterable rows of result. This method - will split the response into chunked DataFrame of a specified size to - to be yielded to the instance calling it. + will return the response as a DataFrame. Args: - max_chunk_size (int): - Maximum number of rows that the DataFrame should contain. - timeout_sec (int): Max no of seconds to wait until job is done. If "timeout_sec" is exceeded, an exception will be raised. @@ -180,14 +176,14 @@ def to_chunked_dataframe( # Max chunk size defined by user for result in self.result(timeout_sec=timeout_sec): - result.append(records) + records.append(result) if len(records) == max_chunk_size: df = pd.DataFrame.from_records(records) records.clear() # Empty records array yield df # Handle for last chunk that is < max_chunk_size - if not records: + if records: yield pd.DataFrame.from_records(records) def __iter__(self): From 4a14a05a6ef67a0f0767467c4ffc9e6b90018f3f Mon Sep 17 00:00:00 2001 From: Zhu Zhan Yan Date: Thu, 17 Sep 2020 11:44:02 +0800 Subject: [PATCH 20/36] Include user identity in fluentd audit logs (#1011) --- common/src/main/java/feast/common/logging/AuditLogger.java | 1 + docs/administration/audit-logging.md | 1 + 2 files changed, 2 insertions(+) diff --git a/common/src/main/java/feast/common/logging/AuditLogger.java b/common/src/main/java/feast/common/logging/AuditLogger.java index e273745f580..0b9901e1ade 100644 --- a/common/src/main/java/feast/common/logging/AuditLogger.java +++ b/common/src/main/java/feast/common/logging/AuditLogger.java @@ -143,6 +143,7 @@ private static void log(Level level, AuditLogEntry entry) { } fluentdLogs.put("id", messageAuditLogEntry.getId()); + fluentdLogs.put("identity", messageAuditLogEntry.getIdentity()); fluentdLogs.put("service", messageAuditLogEntry.getService()); fluentdLogs.put("status_code", messageAuditLogEntry.getStatusCode()); fluentdLogs.put("method", messageAuditLogEntry.getMethod()); diff --git a/docs/administration/audit-logging.md b/docs/administration/audit-logging.md index c11eaeba4c4..c3373399b5f 100644 --- a/docs/administration/audit-logging.md +++ b/docs/administration/audit-logging.md @@ -103,6 +103,7 @@ Feast currently only supports forwarding Request/Response \(Message Audit Log Ty "id": "45329ea9-0d48-46c5-b659-4604f6193711", "service": "CoreService" "status_code": "OK", + "identity": "105960238928959148073", "method": "ListProjects", "request": {}, "response": { From d887b378d2455a0a7a47d7a0207d874921e28ac4 Mon Sep 17 00:00:00 2001 From: Terence Lim Date: Thu, 17 Sep 2020 12:43:02 +0800 Subject: [PATCH 21/36] Update tests with bool list (#1012) --- sdk/python/tests/dataframes.py | 5 +++++ sdk/python/tests/test_client.py | 4 +--- .../all_types_parquet/all_types_parquet.yaml | 2 ++ tests/e2e/redis/basic-ingest-redis-serving.py | 17 ++++++++--------- tests/load/ingest.py | 4 ++++ 5 files changed, 20 insertions(+), 12 deletions(-) diff --git a/sdk/python/tests/dataframes.py b/sdk/python/tests/dataframes.py index a50aa89810c..b07a557c6cc 100644 --- a/sdk/python/tests/dataframes.py +++ b/sdk/python/tests/dataframes.py @@ -116,5 +116,10 @@ np.array([b"one", b"two", b"three"]), np.array([b"one", b"two", b"three"]), ], + "bool_list_feature": [ + [True, False, True], + [True, False, True], + [True, False, True], + ], } ) diff --git a/sdk/python/tests/test_client.py b/sdk/python/tests/test_client.py index b9ef22b95b0..be8bc786794 100644 --- a/sdk/python/tests/test_client.py +++ b/sdk/python/tests/test_client.py @@ -986,9 +986,7 @@ def test_feature_set_types_success(self, test_client, dataframe, mocker): Feature(name="int32_list_feature", dtype=ValueType.INT32_LIST), Feature(name="string_list_feature", dtype=ValueType.STRING_LIST), 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/feast-dev/feast/issues/341 + Feature(name="bool_list_feature", dtype=ValueType.BOOL_LIST), Feature(name="double_list_feature", dtype=ValueType.DOUBLE_LIST), ], max_age=Duration(seconds=3600), diff --git a/tests/e2e/redis/all_types_parquet/all_types_parquet.yaml b/tests/e2e/redis/all_types_parquet/all_types_parquet.yaml index fa95ce13be0..b054913c65a 100644 --- a/tests/e2e/redis/all_types_parquet/all_types_parquet.yaml +++ b/tests/e2e/redis/all_types_parquet/all_types_parquet.yaml @@ -29,4 +29,6 @@ spec: valueType: STRING_LIST - name: bytes_list_feature_parquet valueType: BYTES_LIST + - name: bool_list_feature_parquet + valueType: BOOL_LIST maxAge: 0s diff --git a/tests/e2e/redis/basic-ingest-redis-serving.py b/tests/e2e/redis/basic-ingest-redis-serving.py index 92134af6daa..853da9f5290 100644 --- a/tests/e2e/redis/basic-ingest-redis-serving.py +++ b/tests/e2e/redis/basic-ingest-redis-serving.py @@ -886,12 +886,11 @@ def all_types_dataframe(): np.array([b"one", b"two", b"three"]), np.array([b"one", b"two", b"three"]), ], - # "bool_list_feature": [ - # np.array([True, False, True]), - # np.array([True, False, True]), - # np.array([True, False, True]), - # ], - # TODO: https://github.com/feast-dev/feast/issues/341 + "bool_list_feature": [ + [True, False, True], + [True, False, True], + [True, False, True], + ], } ) @@ -918,6 +917,7 @@ def test_all_types_register_feature_set_success(client): Feature(name="int32_list_feature", dtype=ValueType.INT32_LIST), Feature(name="string_list_feature", dtype=ValueType.STRING_LIST), Feature(name="bytes_list_feature", dtype=ValueType.BYTES_LIST), + Feature(name="bool_list_feature", dtype=ValueType.BOOL_LIST), ], max_age=Duration(seconds=3600), ) @@ -970,6 +970,7 @@ def test_all_types_retrieve_online_success(client, all_types_dataframe): "string_list_feature", "bytes_list_feature", "double_list_feature", + "bool_list_feature", ] def try_get_features(): @@ -1179,12 +1180,10 @@ def all_types_parquet_file(): "bytes_list_feature_parquet": [ np.array([b"one", b"two", b"three"]) for _ in range(COUNT) ], + "bool_list_feature_parquet": [[True, False, True] for _ in range(COUNT)], } ) - # TODO: Boolean list is not being tested. - # 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) return file_path diff --git a/tests/load/ingest.py b/tests/load/ingest.py index cf6e8d7648a..24100d044b3 100644 --- a/tests/load/ingest.py +++ b/tests/load/ingest.py @@ -47,6 +47,9 @@ "bytes_list_feature": [ np.array([b"one", b"two", b"three"]) for _ in range(number_of_entities) ], + "bool_list_feature": [ + [True, False, True] for _ in range(number_of_entities) + ], } ) @@ -67,6 +70,7 @@ Feature(name="int32_list_feature", dtype=ValueType.INT32_LIST), Feature(name="string_list_feature", dtype=ValueType.STRING_LIST), Feature(name="bytes_list_feature", dtype=ValueType.BYTES_LIST), + Feature(name="bool_list_feature", dtype=ValueType.BOOL_LIST), ], ) From 01d2661f80c0f8e608eb2dcd01ae4f379a73df96 Mon Sep 17 00:00:00 2001 From: Zhu Zhan Yan Date: Mon, 21 Sep 2020 10:51:03 +0800 Subject: [PATCH 22/36] Forward port lint version fixes: (#1008) * Fixes issue where lint-version sorts prerelease versions higher than actual stable version. * Refactor out version parsing from tags into get_tag_release common function. * Use get_tag_release in master docker push workflow. --- .github/workflows/master_only.yml | 5 ++- infra/scripts/setup-common-functions.sh | 44 ++++++++++++++++++- infra/scripts/validate-version-consistency.sh | 13 +++--- 3 files changed, 53 insertions(+), 9 deletions(-) diff --git a/.github/workflows/master_only.yml b/.github/workflows/master_only.yml index edbcacceeac..e5ff7980b46 100644 --- a/.github/workflows/master_only.yml +++ b/.github/workflows/master_only.yml @@ -41,6 +41,7 @@ jobs: run: echo ::set-env name=RELEASE_VERSION::${GITHUB_REF#refs/*/} - name: Push versioned Docker image run: | + source infra/scripts/setup-common-functions.sh # Build and push semver tagged commits # Regular expression should match MAJOR.MINOR.PATCH[-PRERELEASE[.IDENTIFIER]] # eg. v0.7.1 v0.7.2-alpha v0.7.2-rc.1 @@ -52,10 +53,10 @@ jobs: docker push gcr.io/kf-feast/feast-${{ matrix.component }}:${VERSION_WITHOUT_PREFIX} # Also update "latest" image if tagged commit is pushed to stable branch - HIGHEST_SEMVER_TAG=$(git tag -l --sort -version:refname | head -n 1) + HIGHEST_SEMVER_TAG=$(get_tag_release -m) echo "Only push to latest tag if tag is the highest semver version $HIGHEST_SEMVER_TAG" - if [ "${VERSION_WITHOUT_PREFIX}" == "${HIGHEST_SEMVER_TAG:1}" ] + if [ "${VERSION_WITHOUT_PREFIX}" = "${HIGHEST_SEMVER_TAG:1}" ] then docker tag gcr.io/kf-feast/feast-${{ matrix.component }}:${GITHUB_SHA} gcr.io/kf-feast/feast-${{ matrix.component }}:latest docker push gcr.io/kf-feast/feast-${{ matrix.component }}:latest diff --git a/infra/scripts/setup-common-functions.sh b/infra/scripts/setup-common-functions.sh index ca65e039f0e..40d5b6badf5 100755 --- a/infra/scripts/setup-common-functions.sh +++ b/infra/scripts/setup-common-functions.sh @@ -173,4 +173,46 @@ wait_for_docker_image(){ done set -$oldopt -} \ No newline at end of file +} + +# Usage: TAG=$(get_tag_release [-ms]) +# Parses the last release from git tags. +# Options: +# -m - Use only tags that are tagged on the current branch +# -s - Use only stable version tags. (ie no prerelease tags). +get_tag_release() { + local GIT_TAG_CMD="git tag -l" + # Match only Semver tags + # Regular expression should match MAJOR.MINOR.PATCH[-PRERELEASE[.IDENTIFIER]] + # eg. v0.7.1 v0.7.2-alpha v0.7.2-rc.1 + local TAG_REGEX='^v[0-9]+\.[0-9]+\.[0-9]+(-([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$' + local OPTIND opt + while getopts "ms" opt; do + case "${opt}" in + m) + GIT_TAG_CMD="$GIT_TAG_CMD --merged" + ;; + s) + # Match only stable version tags. + TAG_REGEX="^v[0-9]+\.[0-9]+\.[0-9]+$" + ;; + *) + echo "get_tag_release(): Error: Bad arguments: $@" + return 1 + ;; + esac + done + shift $((OPTIND-1)) + + # Retrieve tags from git and filter as per regex. + local FILTERED_TAGS=$(bash -c "$GIT_TAG_CMD" | grep -P "$TAG_REGEX") + + # Sort version tags in highest semver version first. + # To make sure that prerelease versions (ie versions vMAJOR.MINOR.PATCH-PRERELEASE suffix) + # are sorted after stable versions (ie vMAJOR.MINOR.PATCH), we append '_' after + # eachustable version as '_' is after '-' found in prerelease version + # alphanumerically and remove after sorting. + local SEMVER_SORTED_TAGS=$(echo "$FILTERED_TAGS" | sed -e '/-/!{s/$/_/}' | sort -rV \ + | sed -e 's/_$//') + echo $(echo "$SEMVER_SORTED_TAGS" | head -n 1) +} diff --git a/infra/scripts/validate-version-consistency.sh b/infra/scripts/validate-version-consistency.sh index 896de18b14e..1f028bf97d6 100755 --- a/infra/scripts/validate-version-consistency.sh +++ b/infra/scripts/validate-version-consistency.sh @@ -9,6 +9,8 @@ # versions against the given merge branch. set -e +source infra/scripts/setup-common-functions.sh + # Fetch tags and current branch git fetch --prune --unshallow --tags || true BRANCH_NAME=${TARGET_MERGE_BRANCH-$(git rev-parse --abbrev-ref HEAD)} @@ -32,8 +34,8 @@ then elif echo "$BRANCH_NAME" | grep -P $RELEASE_BRANCH_REGEX &>/dev/null then # Use last release tag tagged on the release branch - LAST_MERGED_TAG=$(git tag -l --sort -version:refname --merged | head -n 1) - FEAST_MASTER_VERSION=${LAST_MERGED_TAG#"v"} + LAST_MERGED_TAG=$(get_tag_release -m) + FEAST_RELEASE_VERSION=${LAST_MERGED_TAG#"v"} else # Do not enforce version linting as we don't know if the target merge branch FEAST_RELEASE_VERSION="_ANY" FEAST_RELEASE_VERSION="_ANY" @@ -52,12 +54,12 @@ STABLE_TAG_REGEX="^v[0-9]+\.[0-9]+\.[0-9]+$" if [ $BRANCH_NAME = "master" ] then # Use last stable tag repo wide - LAST_STABLE_TAG=$(git tag --sort -version:refname | grep -P "$STABLE_TAG_REGEX" | head -n 1) + LAST_STABLE_TAG=$(get_tag_release -s) FEAST_STABLE_VERSION=${LAST_STABLE_TAG#"v"} elif echo "$BRANCH_NAME" | grep -P $RELEASE_BRANCH_REGEX &>/dev/null then # Use last stable tag tagged on the release branch - LAST_STABLE_MERGE_TAG=$(git tag --sort -version:refname --merged | grep -P "$STABLE_TAG_REGEX" | head -n 1) + LAST_STABLE_MERGE_TAG=$(get_tag_release -sm) FEAST_STABLE_VERSION=${LAST_STABLE_MERGE_TAG#"v"} else # Do not enforce version linting as we don't know if the target merge branch @@ -100,7 +102,6 @@ declare -a files_to_validate_version=( "docs/contributing/development-guide.md,4,${FEAST_MASTER_VERSION}" "docs/administration/audit-logging.md,1,${FEAST_STABLE_VERSION}" "docs/getting-started/deploying-feast/docker-compose.md,1,${FEAST_STABLE_VERSION}" - "docs/getting-started/deploying-feast/kubernetes.md,1,${FEAST_STABLE_VERSION}" "README.md,1,${FEAST_STABLE_VERSION}" "CHANGELOG.md,2,${FEAST_STABLE_VERSION}" ) @@ -123,7 +124,7 @@ for i in "${files_to_validate_version[@]}"; do echo "Testing whether versions are correctly set within file: $FILE_PATH" ACTUAL_OCCURRENCES=$(grep -c -P "\bv?$VERSION\b" "$FILE_PATH" || true) - if [ "${ACTUAL_OCCURRENCES}" -eq "${EXPECTED_OCCURRENCES}" ]; then + if [ "${ACTUAL_OCCURRENCES}" -ge "${EXPECTED_OCCURRENCES}" ]; then echo "OK: Expecting $EXPECTED_OCCURRENCES occurrences of '$VERSION' in $FILE_PATH, and found $ACTUAL_OCCURRENCES" else echo "FAIL: Expecting $EXPECTED_OCCURRENCES occurrences of '$VERSION' in $FILE_PATH, but found $ACTUAL_OCCURRENCES" From 0a322f0c577e3653744743bb5ee7ac73f09a9227 Mon Sep 17 00:00:00 2001 From: Zhu Zhan Yan Date: Mon, 21 Sep 2020 13:02:03 +0800 Subject: [PATCH 23/36] Release v0.7.0 and Bump Versions (#1007) * Update version references in docs to v0.7.0 * Bump master versions to 0.8-SNAPSHOT * Update 0.7 release changelog with hotfix * Delete duplicate entries in changelog --- CHANGELOG.md | 80 +++++++++++++++++++ README.md | 2 +- datatypes/java/README.md | 2 +- docs/administration/audit-logging.md | 2 +- docs/contributing/development-guide.md | 8 +- .../deploying-feast/docker-compose.md | 2 +- infra/charts/feast/Chart.yaml | 2 +- .../charts/feast/charts/feast-core/Chart.yaml | 2 +- .../charts/feast/charts/feast-core/README.md | 2 +- .../charts/feast-jobcontroller/Chart.yaml | 2 +- .../charts/feast-jobcontroller/README.md | 2 +- .../feast/charts/feast-jupyter/Chart.yaml | 2 +- .../feast/charts/feast-jupyter/README.md | 2 +- .../feast/charts/feast-serving/Chart.yaml | 2 +- .../feast/charts/feast-serving/README.md | 2 +- infra/charts/feast/requirements.lock | 8 +- infra/charts/feast/requirements.yaml | 8 +- pom.xml | 2 +- 18 files changed, 106 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02cf0080a08..c2e950538e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,85 @@ # Changelog +## [v0.7.0](https://github.com/feast-dev/feast/tree/v0.7.1) (2020-09-09) + +[Full Changelog](https://github.com/feast-dev/feast/compare/sdk/go/v0.6.2...v0.7.0) + +**Breaking changes:** + +- Add request response logging via fluentd [\#961](https://github.com/feast-dev/feast/pull/961) ([terryyylim](https://github.com/terryyylim)) +- Run JobCoontroller as separate application [\#951](https://github.com/feast-dev/feast/pull/951) ([pyalex](https://github.com/pyalex)) +- Output Subject Claim as Identity in Logging interceptor [\#946](https://github.com/feast-dev/feast/pull/946) ([mrzzy](https://github.com/mrzzy)) +- Use JobManager's backend as persistent storage and source of truth [\#903](https://github.com/feast-dev/feast/pull/903) ([pyalex](https://github.com/pyalex)) +- Fix invalid characters for project, featureset, entity and features creation [\#976](https://github.com/feast-dev/feast/pull/976) ([terryyylim](https://github.com/terryyylim)) + +**Implemented enhancements:** + +- Add redis key prefix as an option to Redis cluster [\#975](https://github.com/feast-dev/feast/pull/975) ([khorshuheng](https://github.com/khorshuheng)) +- Authentication Support for Java & Go SDKs [\#971](https://github.com/feast-dev/feast/pull/971) ([mrzzy](https://github.com/mrzzy)) +- Add configurable prefix to Consumer Group in IngestionJob's Kafka reader [\#969](https://github.com/feast-dev/feast/pull/969) ([terryyylim](https://github.com/terryyylim)) +- Configurable kafka consumer in IngestionJob [\#959](https://github.com/feast-dev/feast/pull/959) ([pyalex](https://github.com/pyalex)) +- Restart Ingestion Job on code version update [\#949](https://github.com/feast-dev/feast/pull/949) ([pyalex](https://github.com/pyalex)) +- Add REST endpoints for Feast UI [\#878](https://github.com/feast-dev/feast/pull/878) ([SwampertX](https://github.com/SwampertX)) +- Upgrade Feast dependencies [\#876](https://github.com/feast-dev/feast/pull/876) ([pyalex](https://github.com/pyalex)) + +**Fixed bugs:** + +- Fix Java & Go SDK TLS support [\#986](https://github.com/feast-dev/feast/pull/986) ([mrzzy](https://github.com/mrzzy)) +- Fix Python SDK setuptools not supporting tags required for Go SDK to be versioned. [\#983](https://github.com/feast-dev/feast/pull/983) ([mrzzy](https://github.com/mrzzy)) +- Fix Python native types multiple entities online retrieval [\#977](https://github.com/feast-dev/feast/pull/977) ([terryyylim](https://github.com/terryyylim)) +- Prevent historical retrieval from failing on dash in project / featureSet name [\#970](https://github.com/feast-dev/feast/pull/970) ([pyalex](https://github.com/pyalex)) +- Fetch Job's labels from dataflow [\#968](https://github.com/feast-dev/feast/pull/968) ([pyalex](https://github.com/pyalex)) +- Fetch Job's Created Datetime from Dataflow [\#966](https://github.com/feast-dev/feast/pull/966) ([pyalex](https://github.com/pyalex)) +- Fix flaky tests [\#953](https://github.com/feast-dev/feast/pull/953) ([pyalex](https://github.com/pyalex)) +- Prevent field duplications on schema merge in BigQuery sink [\#945](https://github.com/feast-dev/feast/pull/945) ([pyalex](https://github.com/pyalex)) +- Fix Audit Message Logging Interceptor Race Condition [\#938](https://github.com/feast-dev/feast/pull/938) ([mrzzy](https://github.com/mrzzy)) +- Bypass authentication for metric endpoints on Serving. [\#936](https://github.com/feast-dev/feast/pull/936) ([mrzzy](https://github.com/mrzzy)) +- Fix grpc security variables name and missing exec qualifier in docker.dev [\#935](https://github.com/feast-dev/feast/pull/935) ([jmelinav](https://github.com/jmelinav)) +- Remove extra line that duplicates statistics list [\#934](https://github.com/feast-dev/feast/pull/934) ([terryyylim](https://github.com/terryyylim)) +- Fix empty array when retrieving stats data [\#930](https://github.com/feast-dev/feast/pull/930) ([terryyylim](https://github.com/terryyylim)) +- Allow unauthenticated access when Authorization is disabled and to Health Probe [\#927](https://github.com/feast-dev/feast/pull/927) ([mrzzy](https://github.com/mrzzy)) +- Impute default project if empty before authorization is called [\#926](https://github.com/feast-dev/feast/pull/926) ([jmelinav](https://github.com/jmelinav)) +- Fix Github Actions CI load-test job failing due inability to install Feast Python SDK. [\#914](https://github.com/feast-dev/feast/pull/914) ([mrzzy](https://github.com/mrzzy)) +- Fix Online Serving unable to retrieve feature data after Feature Set update. [\#908](https://github.com/feast-dev/feast/pull/908) ([mrzzy](https://github.com/mrzzy)) +- Fix unit tests not running in feast.core package. [\#883](https://github.com/feast-dev/feast/pull/883) ([mrzzy](https://github.com/mrzzy)) +- Exclude dependencies signatures from IngestionJob package [\#879](https://github.com/feast-dev/feast/pull/879) ([pyalex](https://github.com/pyalex)) +- Prevent race condition in BQ sink jobId generation [\#877](https://github.com/feast-dev/feast/pull/877) ([pyalex](https://github.com/pyalex)) +- Add IngestionId & EventTimestamp to FeatureRowBatch to calculate lag metric correctly [\#874](https://github.com/feast-dev/feast/pull/874) ([pyalex](https://github.com/pyalex)) +- Fix typo for fluentd request response map key [\#989](https://github.com/feast-dev/feast/pull/989) ([terryyylim](https://github.com/terryyylim)) +- Reduce polling interval for docker-compose test and fix flaky e2e test [\#982](https://github.com/feast-dev/feast/pull/982) ([terryyylim](https://github.com/terryyylim)) +- Fix rate-limiting issue on github actions for master branch [\#974](https://github.com/feast-dev/feast/pull/974) ([terryyylim](https://github.com/terryyylim)) +- Fix docker-compose test [\#973](https://github.com/feast-dev/feast/pull/973) ([terryyylim](https://github.com/terryyylim)) +- Fix Helm chart requirements lock and version linting [\#925](https://github.com/feast-dev/feast/pull/925) ([woop](https://github.com/woop)) +- Fix Github Actions failures due to possible rate limiting. [\#972](https://github.com/feast-dev/feast/pull/972) ([mrzzy](https://github.com/mrzzy)) +- Fix docker image building for PR commits [\#907](https://github.com/feast-dev/feast/pull/907) ([woop](https://github.com/woop)) +- Fix Github Actions versioned image push [\#994](https://github.com/feast-dev/feast/pull/994)([mrzzy](https://github.com/mrzzy)) +- Fix Go SDK extra colon in metadata header for Authentication [\#1001](https://github.com/feast-dev/feast/pull/1001)([mrzzy](https://github.com/mrzzy)) +- Fix lint version not pulling tags. [\#999](https://github.com/feast-dev/feast/pull/999)([mrzzy](https://github.com/mrzzy)) +- Call fallback only when theres missing keys [\#1009](https://github.com/feast-dev/feast/pull/751) ([pyalex](https://github.com/pyalex)) + +**Merged pull requests:** + +- Add cryptography to python ci-requirements [\#988](https://github.com/feast-dev/feast/pull/988) ([pyalex](https://github.com/pyalex)) +- Allow maps in environment variables in helm charts [\#987](https://github.com/feast-dev/feast/pull/987) ([pyalex](https://github.com/pyalex)) +- Speed up Github Actions Docker builds [\#980](https://github.com/feast-dev/feast/pull/980) ([mrzzy](https://github.com/mrzzy)) +- Use setup.py develop instead of pip install -e [\#967](https://github.com/feast-dev/feast/pull/967) ([pyalex](https://github.com/pyalex)) +- Peg black version [\#963](https://github.com/feast-dev/feast/pull/963) ([terryyylim](https://github.com/terryyylim)) +- Remove FeatureRow compaction in BQ sink [\#960](https://github.com/feast-dev/feast/pull/960) ([pyalex](https://github.com/pyalex)) +- Get job controller deployment for docker compose back [\#958](https://github.com/feast-dev/feast/pull/958) ([pyalex](https://github.com/pyalex)) +- Revert job controller deployment for docker compose [\#957](https://github.com/feast-dev/feast/pull/957) ([woop](https://github.com/woop)) +- JobCoordinator use public API to communicate with Core [\#943](https://github.com/feast-dev/feast/pull/943) ([pyalex](https://github.com/pyalex)) +- Allow Logging Interceptor to be toggled by Message Logging Enabled Flag [\#940](https://github.com/feast-dev/feast/pull/940) ([mrzzy](https://github.com/mrzzy)) +- Clean up Feast CI, docker compose, and notebooks [\#916](https://github.com/feast-dev/feast/pull/916) ([woop](https://github.com/woop)) +- Allow use of Kubernetes for Github Actions [\#910](https://github.com/feast-dev/feast/pull/910) ([woop](https://github.com/woop)) +- Wait for docker images to be ready for e2e dataflow test [\#909](https://github.com/feast-dev/feast/pull/909) ([woop](https://github.com/woop)) +- Add docker image building to GitHub Actions and consolidate workflows [\#898](https://github.com/feast-dev/feast/pull/898) ([woop](https://github.com/woop)) +- Add load test GitHub Action [\#897](https://github.com/feast-dev/feast/pull/897) ([woop](https://github.com/woop)) +- Typo in feature sets example. [\#894](https://github.com/feast-dev/feast/pull/894) ([ashwinath](https://github.com/ashwinath)) +- Add auth integration tests [\#892](https://github.com/feast-dev/feast/pull/892) ([woop](https://github.com/woop)) +- Integration Test for Job Coordinator [\#886](https://github.com/feast-dev/feast/pull/886) ([pyalex](https://github.com/pyalex)) +- BQ sink produces sample of successful inserts [\#875](https://github.com/feast-dev/feast/pull/875) ([pyalex](https://github.com/pyalex)) +- Add Branch and RC Awareness to Version Lint & Fix Semver Regex [\#998](https://github.com/feast-dev/feast/pull/998) ([mrzzy](https://github.com/mrzzy)) + ## [v0.6.2](https://github.com/feast-dev/feast/tree/v0.6.2) (2020-08-02) [Full Changelog](https://github.com/feast-dev/feast/compare/v0.6.1...v0.6.2) diff --git a/README.md b/README.md index 450722c14bb..5463d17ba50 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ prediction = my_model.predict(fs.get_online_features(customer_features, customer Clone the latest stable version of the [Feast repository](https://github.com/gojek/feast/) and navigate to the `infra/docker-compose` sub-directory: ``` -git clone --depth 1 --branch v0.6.2 https://github.com/feast-dev/feast.git +git clone --depth 1 --branch v0.7.0 https://github.com/feast-dev/feast.git cd feast/infra/docker-compose cp .env.sample .env ``` diff --git a/datatypes/java/README.md b/datatypes/java/README.md index 5a5deb04296..ce43fe07132 100644 --- a/datatypes/java/README.md +++ b/datatypes/java/README.md @@ -16,7 +16,7 @@ Dependency Coordinates dev.feast datatypes-java - 0.7-SNAPSHOT + 0.8-SNAPSHOT ``` diff --git a/docs/administration/audit-logging.md b/docs/administration/audit-logging.md index c3373399b5f..cbeb84f068a 100644 --- a/docs/administration/audit-logging.md +++ b/docs/administration/audit-logging.md @@ -45,7 +45,7 @@ Audit Logs produced by Feast are written to the console similar to normal logs b "service": "CoreService", "component": "feast-core", "id": "45329ea9-0d48-46c5-b659-4604f6193711", - "version": "0.6.2" + "version": "0.7.0" }, "hostname": "feast.core" "timestamp": "2020-08-16T04:45:24Z", diff --git a/docs/contributing/development-guide.md b/docs/contributing/development-guide.md index 2463fa48104..4e1d0a1ee8a 100644 --- a/docs/contributing/development-guide.md +++ b/docs/contributing/development-guide.md @@ -132,7 +132,7 @@ To run Feast Core locally: ```bash # Feast Core can be configured from the following .yml file # $FEAST_REPO/core/src/main/resources/application.yml -java -jar core/target/feast-core-0.7-SNAPSHOT-exec.jar +java -jar core/target/feast-core-0.8-SNAPSHOT-exec.jar ``` Test whether Feast Core is running @@ -156,7 +156,7 @@ To run Feast Job Controller locally: ```bash # Feast Job Controller can be configured from the following .yml file # $FEAST_REPO/job-controller/src/main/resources/application.yml -java -jar job-controller/target/feast-job-controller-0.7-SNAPSHOT-exec.jar +java -jar job-controller/target/feast-job-controller-0.8-SNAPSHOT-exec.jar ``` Test whether Feast Job Controller is running: @@ -199,7 +199,7 @@ Once Feast Serving is started, it will register its store with Feast Core \(by n Start Feast Serving server on localhost:6566: ```text -java -jar serving/target/feast-serving-0.7-SNAPSHOT-exec.jar +java -jar serving/target/feast-serving-0.8-SNAPSHOT-exec.jar ``` Test connectivity to Feast Serving @@ -210,7 +210,7 @@ grpc_cli call localhost:6566 GetFeastServingInfo '' ```text connecting to localhost:6566 -version: "0.7-SNAPSHOT" +version: "0.8-SNAPSHOT" type: FEAST_SERVING_TYPE_ONLINE Rpc succeeded with OK status diff --git a/docs/getting-started/deploying-feast/docker-compose.md b/docs/getting-started/deploying-feast/docker-compose.md index 365a6c464e8..b7d5da1ae3a 100644 --- a/docs/getting-started/deploying-feast/docker-compose.md +++ b/docs/getting-started/deploying-feast/docker-compose.md @@ -18,7 +18,7 @@ The Docker Compose setup is recommended if you are running Feast locally to try Clone the latest stable version of the [Feast repository](https://github.com/gojek/feast/) and setup before we deploy: ```text -git clone --depth 1 --branch v0.6.2 https://github.com/feast-dev/feast.git +git clone --depth 1 --branch v0.7.0 https://github.com/feast-dev/feast.git export FEAST_REPO=$(pwd) cd feast/infra/docker-compose cp .env.sample .env diff --git a/infra/charts/feast/Chart.yaml b/infra/charts/feast/Chart.yaml index f2e1c49c877..bc730fd4b20 100644 --- a/infra/charts/feast/Chart.yaml +++ b/infra/charts/feast/Chart.yaml @@ -1,4 +1,4 @@ apiVersion: v1 description: Feature store for machine learning. name: feast -version: 0.7-SNAPSHOT +version: 0.8-SNAPSHOT diff --git a/infra/charts/feast/charts/feast-core/Chart.yaml b/infra/charts/feast/charts/feast-core/Chart.yaml index 56ab15e7fbc..502e5841968 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: Feast Core registers feature specifications. name: feast-core -version: 0.7-SNAPSHOT +version: 0.8-SNAPSHOT diff --git a/infra/charts/feast/charts/feast-core/README.md b/infra/charts/feast/charts/feast-core/README.md index af9370dd72c..2802cb5b791 100644 --- a/infra/charts/feast/charts/feast-core/README.md +++ b/infra/charts/feast/charts/feast-core/README.md @@ -2,7 +2,7 @@ feast-core ========== Feast Core registers feature specifications. -Current chart version is `0.7-SNAPSHOT` +Current chart version is `0.8-SNAPSHOT` diff --git a/infra/charts/feast/charts/feast-jobcontroller/Chart.yaml b/infra/charts/feast/charts/feast-jobcontroller/Chart.yaml index ae0aad538ff..6c00a3a55f6 100644 --- a/infra/charts/feast/charts/feast-jobcontroller/Chart.yaml +++ b/infra/charts/feast/charts/feast-jobcontroller/Chart.yaml @@ -1,4 +1,4 @@ apiVersion: v1 description: Feast Job Coontroller manage ingestion jobs. name: feast-jobcontroller -version: 0.7-SNAPSHOT +version: 0.8-SNAPSHOT diff --git a/infra/charts/feast/charts/feast-jobcontroller/README.md b/infra/charts/feast/charts/feast-jobcontroller/README.md index 628a69f480f..a10fcae8861 100644 --- a/infra/charts/feast/charts/feast-jobcontroller/README.md +++ b/infra/charts/feast/charts/feast-jobcontroller/README.md @@ -2,7 +2,7 @@ feast-jobcontroller ========== Feast Job Controller manage ingestion jobs. -Current chart version is `0.7-SNAPSHOT` +Current chart version is `0.8-SNAPSHOT` diff --git a/infra/charts/feast/charts/feast-jupyter/Chart.yaml b/infra/charts/feast/charts/feast-jupyter/Chart.yaml index 36ce0cf9ef1..c2277567fd4 100644 --- a/infra/charts/feast/charts/feast-jupyter/Chart.yaml +++ b/infra/charts/feast/charts/feast-jupyter/Chart.yaml @@ -1,4 +1,4 @@ apiVersion: v1 description: Feast Jupyter provides a Jupyter server with pre-installed Feast SDK name: feast-jupyter -version: 0.7-SNAPSHOT +version: 0.8-SNAPSHOT diff --git a/infra/charts/feast/charts/feast-jupyter/README.md b/infra/charts/feast/charts/feast-jupyter/README.md index ec9567190df..427d3c458a8 100644 --- a/infra/charts/feast/charts/feast-jupyter/README.md +++ b/infra/charts/feast/charts/feast-jupyter/README.md @@ -2,7 +2,7 @@ feast-jupyter ============= Feast Jupyter provides a Jupyter server with pre-installed Feast SDK -Current chart version is `0.7-SNAPSHOT` +Current chart version is `0.8-SNAPSHOT` diff --git a/infra/charts/feast/charts/feast-serving/Chart.yaml b/infra/charts/feast/charts/feast-serving/Chart.yaml index 5ad3f624d22..65b0da4a3bb 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: Feast Serving serves low-latency latest features and historical batch features. name: feast-serving -version: 0.7-SNAPSHOT +version: 0.8-SNAPSHOT diff --git a/infra/charts/feast/charts/feast-serving/README.md b/infra/charts/feast/charts/feast-serving/README.md index 16f39ef5694..fa3553fb8b2 100644 --- a/infra/charts/feast/charts/feast-serving/README.md +++ b/infra/charts/feast/charts/feast-serving/README.md @@ -2,7 +2,7 @@ feast-serving ============= Feast Serving serves low-latency latest features and historical batch features. -Current chart version is `0.7-SNAPSHOT` +Current chart version is `0.8-SNAPSHOT` diff --git a/infra/charts/feast/requirements.lock b/infra/charts/feast/requirements.lock index 9844cc048c0..0c0bda5fadf 100644 --- a/infra/charts/feast/requirements.lock +++ b/infra/charts/feast/requirements.lock @@ -1,16 +1,16 @@ dependencies: - name: feast-core repository: "" - version: 0.7-SNAPSHOT + version: 0.8-SNAPSHOT - name: feast-serving repository: "" - version: 0.7-SNAPSHOT + version: 0.8-SNAPSHOT - name: feast-serving repository: "" - version: 0.7-SNAPSHOT + version: 0.8-SNAPSHOT - name: feast-jupyter repository: "" - version: 0.7-SNAPSHOT + version: 0.8-SNAPSHOT - name: postgresql repository: https://kubernetes-charts.storage.googleapis.com/ version: 8.6.1 diff --git a/infra/charts/feast/requirements.yaml b/infra/charts/feast/requirements.yaml index 40bb4f59a35..3f5c058f778 100644 --- a/infra/charts/feast/requirements.yaml +++ b/infra/charts/feast/requirements.yaml @@ -1,17 +1,17 @@ dependencies: - name: feast-core - version: 0.7-SNAPSHOT + version: 0.8-SNAPSHOT condition: feast-core.enabled - name: feast-serving alias: feast-online-serving - version: 0.7-SNAPSHOT + version: 0.8-SNAPSHOT condition: feast-online-serving.enabled - name: feast-serving alias: feast-batch-serving - version: 0.7-SNAPSHOT + version: 0.8-SNAPSHOT condition: feast-batch-serving.enabled - name: feast-jupyter - version: 0.7-SNAPSHOT + version: 0.8-SNAPSHOT condition: feast-jupyter.enabled - name: postgresql version: 8.6.1 diff --git a/pom.xml b/pom.xml index b8a9fe9b7b9..fd3faf5622f 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ - 0.7-SNAPSHOT + 0.8-SNAPSHOT https://github.com/feast-dev/feast UTF-8 From f5a5640f9c93898898f8cc90be22f8f813c149ad Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Tue, 22 Sep 2020 01:36:53 +0000 Subject: [PATCH 24/36] GitBook: [master] 37 pages modified --- docs/administration/audit-logging.md | 20 +++++++++---------- docs/administration/security.md | 2 +- .../deploying-feast/docker-compose.md | 10 +++++----- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/administration/audit-logging.md b/docs/administration/audit-logging.md index cbeb84f068a..fb7b181cb2d 100644 --- a/docs/administration/audit-logging.md +++ b/docs/administration/audit-logging.md @@ -1,10 +1,10 @@ # Audit Logging -### Introduction +## Introduction Feast provides audit logging functionality in order to debug problems and to trace the lineage of events. -### Audit Log Types +## Audit Log Types Audit Logs produced by Feast come in three favors: @@ -12,17 +12,17 @@ Audit Logs produced by Feast come in three favors: | :--- | :--- | | Message Audit Log | Logs service calls that can be used to track Feast request handling. Currently only gRPC request/response is supported. Enabling Message Audit Logs can be resource intensive and significantly increase latency, as such is not recommended on Online Serving. | | Transition Audit Log | Logs transitions in status in resources managed by Feast \(ie an Ingestion Job becoming RUNNING\). | -| Action Audit Log | Logs actions performed on a specific resource managed by Feast \(ie an Ingestion Job is aborted\). | +| Action Audit Log | Logs actions performed on a specific resource managed by Feast \(ie an Ingestion Job is aborted\). | -### Configuration +## Configuration | Audit Log Type | Description | | :--- | :--- | | Message Audit Log | Enabled when both `feast.logging.audit.enabled` and `feast.logging.audit.messageLogging.enabled` is set to `true` | | Transition Audit Log | Enabled when `feast.logging.audit.enabled` is set to `true` | -| Action Audit Log | Enabled when `feast.logging.audit.enabled` is set to `true` | +| Action Audit Log | Enabled when `feast.logging.audit.enabled` is set to `true` | -### JSON Format +## JSON Format Audit Logs produced by Feast are written to the console similar to normal logs but in a structured, machine parsable JSON. Example of a Message Audit Log JSON entry produced: @@ -53,7 +53,7 @@ Audit Logs produced by Feast are written to the console similar to normal logs b } ``` -### Log Entry Schema +## Log Entry Schema Fields common to all Audit Log Types: @@ -92,11 +92,11 @@ Fields in Transition Audit Log Type | `resource.type` | Type of resource of which the transition occurred \(ie `FeatureSet`\) | | `resource.id` | Identifier specifying the specific resource of which the transition occurred. | -### Log Forwarder +## Log Forwarder Feast currently only supports forwarding Request/Response \(Message Audit Log Type\) logs to an external fluentD service with `feast.**` Fluentd tag. -#### Request/Response Log Example +### Request/Response Log Example ```text { @@ -115,7 +115,7 @@ Feast currently only supports forwarding Request/Response \(Message Audit Log Ty } ``` -#### Configuration +### Configuration The Fluentd Log Forwarder configured with the with the following configuration options in `application.yml`: diff --git a/docs/administration/security.md b/docs/administration/security.md index 6a4d336d665..2350c76440b 100644 --- a/docs/administration/security.md +++ b/docs/administration/security.md @@ -450,7 +450,7 @@ Authorization provides access control to FeatureSets/Features based on project m ![Feast Authorization Flow](../.gitbook/assets/rsz_untitled23.jpg) -Feast delegates Authorization grants to a external Authorization Server that implements the [Authorization Open API specification](https://github.com/feast-dev/feast/blob/master/auth/src/main/resources/api.yaml). +Feast delegates Authorization grants to a external Authorization Server that implements the [Authorization Open API specification](https://github.com/feast-dev/feast/blob/master/common/src/main/resources/api.yaml). * Feast checks whether a user is authorized to make a request by making a `checkAccessRequest` to the Authorization Server. * The Authorization Server should return a `AuthorizationResult` with whether user is allowed to make the request. diff --git a/docs/getting-started/deploying-feast/docker-compose.md b/docs/getting-started/deploying-feast/docker-compose.md index b7d5da1ae3a..166a1bfffdd 100644 --- a/docs/getting-started/deploying-feast/docker-compose.md +++ b/docs/getting-started/deploying-feast/docker-compose.md @@ -2,9 +2,9 @@ ### Overview -This guide will give a walk-though on deploying Feast using Docker Compose. +This guide will give a walk-though on deploying Feast using Docker Compose. -The Docker Compose setup is recommended if you are running Feast locally to try things out. It includes a built in Jupyter Notebook Server that is preloaded with Feast example notebooks to get you started. +The Docker Compose setup is recommended if you are running Feast locally to try things out. It includes a built in Jupyter Notebook Server that is preloaded with Feast example notebooks to get you started. ## 0. Requirements @@ -43,7 +43,7 @@ The Docker Compose deployment will take some time fully startup: {% endhint %} {% hint style="info" %} - You may see `feast_historical_serving` exiting with code 1, this expected and does not affect the functionality of Feast for Online Serving. +You may see `feast_historical_serving` exiting with code 1, this expected and does not affect the functionality of Feast for Online Serving. {% endhint %} Once deployed, you should be able to connect at `localhost:8888` to the bundled Jupyter Notebook Server and follow in the Online Serving sections of the example notebooks: @@ -53,12 +53,12 @@ Once deployed, you should be able to connect at `localhost:8888` to the bundled ## 3. Start Feast for Training and Online Serving {% hint style="info" %} -Historical serving currently requires Google Cloud Platform to function, specifically a Service Account with access to Google Cloud Storage \(GCS\) and BigQuery. +Historical serving currently requires Google Cloud Platform to function, specifically a Service Account with access to Google Cloud Storage \(GCS\) and BigQuery. {% endhint %} ### 3.1 Set up Google Cloud Platform -Create a service account for Feast to use. Make sure to copy the JSON key to `infra/docker-compose/gcp-service-accounts/key.json` under the cloned Feast repository. +Create a service account for Feast to use. Make sure to copy the JSON key to `infra/docker-compose/gcp-service-accounts/key.json` under the cloned Feast repository. ```bash gcloud iam service-accounts create feast-service-account From dbd8b715b474d2b480ec1e7976469655900d3a66 Mon Sep 17 00:00:00 2001 From: Willem Pienaar Date: Sat, 26 Sep 2020 05:31:18 +0000 Subject: [PATCH 25/36] GitBook: [master] 37 pages modified --- docs/roadmap.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/roadmap.md b/docs/roadmap.md index 6272227a5db..ace85ff4103 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,5 +1,25 @@ # Roadmap +## Feast 0.8 + +[Discussion](https://github.com/feast-dev/feast/issues/1018) + +[Feast 0.8 RFC](https://docs.google.com/document/d/1snRxVb8ipWZjCiLlfkR4Oc28p7Fkv_UXjvxBFWjRBj4/edit#heading=h.yvkhw2cuvx5) + +### **New Functionality** + +1. Add support for AWS \(data sources and deployment\) +2. Add support for local deployment +3. Add support for Spark based ingestion +4. Add support for Spark based historical retrieval + +### **Technical debt, refactoring, or housekeeping** + +1. Move job management functionality to SDK +2. Remove Apache Beam based ingestion +3. Allow direct ingestion from batch sources that does not pass through stream +4. Remove Feast Historical Serving abstraction to allow direct access from Feast SDK to data sources for retrieval + ## Feast 0.7 [Discussion](https://github.com/feast-dev/feast/issues/834) From 87ee594ebaf94c965342052655533508b93a237a Mon Sep 17 00:00:00 2001 From: Terence Lim Date: Mon, 28 Sep 2020 12:29:05 +0800 Subject: [PATCH 26/36] Introduce Entity as higher-level concept (#1014) * Add entity data generator * Update core protos * Add entities as higher-level concept * Update python sdk * Add tests * Update to use scalar entity * Address PR comments * Update proto and tests * Remove redis pytest * Remove wildcard filters * Update golang generated files * Update tests * Rename constraints * Update list entities method * Update go protos --- .../java/feast/common/it/DataGenerator.java | 18 +- .../feast/common/it/SimpleCoreClient.java | 49 + .../java/feast/core/dao/EntityRepository.java | 33 + .../java/feast/core/grpc/CoreServiceImpl.java | 87 + .../main/java/feast/core/model/EntityV2.java | 145 ++ .../main/java/feast/core/model/Project.java | 13 + .../java/feast/core/service/SpecService.java | 147 ++ .../java/feast/core/util/TypeConversion.java | 25 + .../core/validators/EntityValidator.java | 37 + .../V2.7__Entities_Higher_Level_Concept.sql | 21 + .../feast/core/service/SpecServiceIT.java | 230 +- go.mod | 8 +- go.sum | 13 + protos/feast/core/CoreService.proto | 66 + protos/feast/core/Entity.proto | 50 + sdk/go/protos/feast/core/CoreService.pb.go | 2101 ++++++++++++----- sdk/go/protos/feast/core/Entity.pb.go | 379 +++ sdk/go/protos/feast/core/FeatureSet.pb.go | 4 +- .../feast/core/FeatureSetReference.pb.go | 4 +- sdk/go/protos/feast/core/IngestionJob.pb.go | 149 +- sdk/go/protos/feast/core/Runner.pb.go | 161 +- sdk/go/protos/feast/core/Source.pb.go | 4 +- sdk/go/protos/feast/core/Store.pb.go | 183 +- .../protos/feast/serving/ServingService.pb.go | 543 +++-- sdk/go/protos/feast/storage/Redis.pb.go | 4 +- sdk/go/protos/feast/types/FeatureRow.pb.go | 4 +- .../feast/types/FeatureRowExtended.pb.go | 4 +- sdk/go/protos/feast/types/Field.pb.go | 4 +- sdk/go/protos/feast/types/Value.pb.go | 4 +- .../tensorflow_metadata/proto/v0/path.pb.go | 4 +- .../tensorflow_metadata/proto/v0/schema.pb.go | 4 +- .../proto/v0/statistics.pb.go | 4 +- sdk/python/feast/cli.py | 94 + sdk/python/feast/client.py | 148 +- sdk/python/feast/entity.py | 281 +++ sdk/python/tests/test_entity.py | 86 + 36 files changed, 4022 insertions(+), 1089 deletions(-) create mode 100644 core/src/main/java/feast/core/dao/EntityRepository.java create mode 100644 core/src/main/java/feast/core/model/EntityV2.java create mode 100644 core/src/main/java/feast/core/validators/EntityValidator.java create mode 100644 core/src/main/resources/db/migration/V2.7__Entities_Higher_Level_Concept.sql create mode 100644 protos/feast/core/Entity.proto create mode 100644 sdk/go/protos/feast/core/Entity.pb.go create mode 100644 sdk/python/tests/test_entity.py diff --git a/common-test/src/main/java/feast/common/it/DataGenerator.java b/common-test/src/main/java/feast/common/it/DataGenerator.java index 17da60b0cf8..6c5428ff171 100644 --- a/common-test/src/main/java/feast/common/it/DataGenerator.java +++ b/common-test/src/main/java/feast/common/it/DataGenerator.java @@ -17,6 +17,7 @@ package feast.common.it; import com.google.common.collect.ImmutableList; +import feast.proto.core.EntityProto; import feast.proto.core.FeatureSetProto; import feast.proto.core.SourceProto; import feast.proto.core.StoreProto; @@ -111,11 +112,24 @@ public static FeatureSetProto.FeatureSpec createFeature( .build(); } - public static FeatureSetProto.EntitySpec createEntity( + public static FeatureSetProto.EntitySpec createEntitySpec( String name, ValueProto.ValueType.Enum valueType) { return FeatureSetProto.EntitySpec.newBuilder().setName(name).setValueType(valueType).build(); } + public static EntityProto.EntitySpecV2 createEntitySpecV2( + String name, + String description, + ValueProto.ValueType.Enum valueType, + Map labels) { + return EntityProto.EntitySpecV2.newBuilder() + .setName(name) + .setDescription(description) + .setValueType(valueType) + .putAllLabels(labels) + .build(); + } + public static FeatureSetProto.FeatureSet createFeatureSet( SourceProto.Source source, String projectName, @@ -152,7 +166,7 @@ public static FeatureSetProto.FeatureSet createFeatureSet( .putAllLabels(labels) .addAllEntities( entities.entrySet().stream() - .map(entry -> createEntity(entry.getKey(), entry.getValue())) + .map(entry -> createEntitySpec(entry.getKey(), entry.getValue())) .collect(Collectors.toList())) .addAllFeatures( features.entrySet().stream() diff --git a/common-test/src/main/java/feast/common/it/SimpleCoreClient.java b/common-test/src/main/java/feast/common/it/SimpleCoreClient.java index 4b38886b1bd..6f01d83bb63 100644 --- a/common-test/src/main/java/feast/common/it/SimpleCoreClient.java +++ b/common-test/src/main/java/feast/common/it/SimpleCoreClient.java @@ -35,6 +35,46 @@ public CoreServiceProto.ApplyFeatureSetResponse simpleApplyFeatureSet( CoreServiceProto.ApplyFeatureSetRequest.newBuilder().setFeatureSet(featureSet).build()); } + public CoreServiceProto.ApplyEntityResponse simpleApplyEntity( + String projectName, EntityProto.EntitySpecV2 spec) { + return stub.applyEntity( + CoreServiceProto.ApplyEntityRequest.newBuilder() + .setProject(projectName) + .setSpec(spec) + .build()); + } + + public List simpleListEntities(String projectName) { + return stub.listEntities( + CoreServiceProto.ListEntitiesRequest.newBuilder() + .setFilter( + CoreServiceProto.ListEntitiesRequest.Filter.newBuilder() + .setProject(projectName) + .build()) + .build()) + .getEntitiesList(); + } + + public List simpleListEntities( + String projectName, Map labels) { + return stub.listEntities( + CoreServiceProto.ListEntitiesRequest.newBuilder() + .setFilter( + CoreServiceProto.ListEntitiesRequest.Filter.newBuilder() + .setProject(projectName) + .putAllLabels(labels) + .build()) + .build()) + .getEntitiesList(); + } + + public List simpleListEntities( + CoreServiceProto.ListEntitiesRequest.Filter filter) { + return stub.listEntities( + CoreServiceProto.ListEntitiesRequest.newBuilder().setFilter(filter).build()) + .getEntitiesList(); + } + public List simpleListFeatureSets( String projectName, String featureSetName, Map labels) { return stub.listFeatureSets( @@ -82,6 +122,15 @@ public FeatureSetProto.FeatureSet simpleGetFeatureSet(String projectName, String .getFeatureSet(); } + public EntityProto.Entity simpleGetEntity(String projectName, String name) { + return stub.getEntity( + CoreServiceProto.GetEntityRequest.newBuilder() + .setName(name) + .setProject(projectName) + .build()) + .getEntity(); + } + public void updateFeatureSetStatus( String projectName, String name, FeatureSetProto.FeatureSetStatus status) { stub.updateFeatureSetStatus( diff --git a/core/src/main/java/feast/core/dao/EntityRepository.java b/core/src/main/java/feast/core/dao/EntityRepository.java new file mode 100644 index 00000000000..d7d7dcb5b9f --- /dev/null +++ b/core/src/main/java/feast/core/dao/EntityRepository.java @@ -0,0 +1,33 @@ +/* + * 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.dao; + +import feast.core.model.EntityV2; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +/** JPA repository supplying EntityV2 objects keyed by id. */ +public interface EntityRepository extends JpaRepository { + + long count(); + + // Find all EntityV2s by project + List findAllByProject_Name(String project); + + // Find single EntityV2 by project and name + EntityV2 findEntityByNameAndProject_Name(String name, String project); +} diff --git a/core/src/main/java/feast/core/grpc/CoreServiceImpl.java b/core/src/main/java/feast/core/grpc/CoreServiceImpl.java index 59abf24fdf5..3a3106ee1bc 100644 --- a/core/src/main/java/feast/core/grpc/CoreServiceImpl.java +++ b/core/src/main/java/feast/core/grpc/CoreServiceImpl.java @@ -28,6 +28,7 @@ import feast.core.service.StatsService; import feast.proto.core.CoreServiceGrpc.CoreServiceImplBase; import feast.proto.core.CoreServiceProto.*; +import feast.proto.core.EntityProto.EntitySpecV2; import feast.proto.core.FeatureSetProto.FeatureSet; import io.grpc.Status; import io.grpc.StatusRuntimeException; @@ -95,6 +96,31 @@ public void getFeatureSet( } } + @Override + public void getEntity( + GetEntityRequest request, StreamObserver responseObserver) { + try { + GetEntityResponse response = specService.getEntity(request); + responseObserver.onNext(response); + responseObserver.onCompleted(); + } catch (RetrievalException e) { + log.error("Unable to fetch entity requested in GetEntity method: ", e); + responseObserver.onError( + Status.NOT_FOUND.withDescription(e.getMessage()).withCause(e).asRuntimeException()); + } catch (IllegalArgumentException e) { + log.error("Illegal arguments provided to GetEntity method: ", e); + responseObserver.onError( + Status.INVALID_ARGUMENT + .withDescription(e.getMessage()) + .withCause(e) + .asRuntimeException()); + } catch (Exception e) { + log.error("Exception has occurred in GetEntity method: ", e); + responseObserver.onError( + Status.INTERNAL.withDescription(e.getMessage()).withCause(e).asRuntimeException()); + } + } + @Override public void listFeatureSets( ListFeatureSetsRequest request, StreamObserver responseObserver) { @@ -135,6 +161,32 @@ public void listFeatures( } } + /** Retrieve a list of entities */ + @Override + public void listEntities( + ListEntitiesRequest request, StreamObserver responseObserver) { + try { + ListEntitiesResponse response = specService.listEntities(request.getFilter()); + responseObserver.onNext(response); + responseObserver.onCompleted(); + } catch (IllegalArgumentException e) { + log.error("Illegal arguments provided to ListEntities method: ", e); + responseObserver.onError( + Status.INVALID_ARGUMENT + .withDescription(e.getMessage()) + .withCause(e) + .asRuntimeException()); + } catch (RetrievalException e) { + log.error("Unable to fetch entities requested in ListEntities method: ", e); + responseObserver.onError( + Status.NOT_FOUND.withDescription(e.getMessage()).withCause(e).asRuntimeException()); + } catch (Exception e) { + log.error("Exception has occurred in ListEntities method: ", e); + responseObserver.onError( + Status.INTERNAL.withDescription(e.getMessage()).withCause(e).asRuntimeException()); + } + } + @Override public void getFeatureStatistics( GetFeatureStatisticsRequest request, @@ -191,6 +243,41 @@ public void listStores( } } + /* Registers an entity to Feast Core */ + @Override + public void applyEntity( + ApplyEntityRequest request, StreamObserver responseObserver) { + + String projectId = null; + + try { + EntitySpecV2 spec = request.getSpec(); + projectId = request.getProject(); + authorizationService.authorizeRequest(SecurityContextHolder.getContext(), projectId); + ApplyEntityResponse response = specService.applyEntity(spec, projectId); + responseObserver.onNext(response); + responseObserver.onCompleted(); + } catch (org.hibernate.exception.ConstraintViolationException e) { + log.error( + "Unable to persist this entity due to a constraint violation. Please ensure that" + + " field names are unique within the project namespace: ", + e); + responseObserver.onError( + Status.ALREADY_EXISTS.withDescription(e.getMessage()).withCause(e).asRuntimeException()); + } catch (AccessDeniedException e) { + log.info(String.format("User prevented from accessing project: %s", projectId)); + responseObserver.onError( + Status.PERMISSION_DENIED + .withDescription(e.getMessage()) + .withCause(e) + .asRuntimeException()); + } catch (Exception e) { + log.error("Exception has occurred in ApplyEntity method: ", e); + responseObserver.onError( + Status.INTERNAL.withDescription(e.getMessage()).withCause(e).asRuntimeException()); + } + } + @Override public void applyFeatureSet( ApplyFeatureSetRequest request, StreamObserver responseObserver) { diff --git a/core/src/main/java/feast/core/model/EntityV2.java b/core/src/main/java/feast/core/model/EntityV2.java new file mode 100644 index 00000000000..aeb6728454a --- /dev/null +++ b/core/src/main/java/feast/core/model/EntityV2.java @@ -0,0 +1,145 @@ +/* + * 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 com.google.protobuf.Timestamp; +import feast.core.util.TypeConversion; +import feast.proto.core.EntityProto; +import feast.proto.core.EntityProto.*; +import feast.proto.types.ValueProto.ValueType; +import java.util.Map; +import javax.persistence.*; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@javax.persistence.Entity +@Table( + name = "entities_v2", + uniqueConstraints = @UniqueConstraint(columnNames = {"name", "project_name"})) +public class EntityV2 extends AbstractTimestampEntity { + @Id @GeneratedValue private long id; + + // Name of the Entity + @Column(name = "name", nullable = false) + private String name; + + // Project that this Entity belongs to + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "project_name") + private Project project; + + // Description of entity + @Column(name = "description", columnDefinition = "text") + private String description; + + // Columns of entities + /** Data type of each entity column: String representation of {@link ValueType} * */ + private String type; + + // User defined metadata + @Column(name = "labels", columnDefinition = "text") + private String labels; + + public EntityV2() { + super(); + } + + /** + * EntityV2 object supports Entity registration in FeatureTable. + * + *

This data model supports Scalar Entity and would allow ease of discovery of entities and + * reasoning when used in association with FeatureTable. + */ + public EntityV2( + String name, String description, ValueType.Enum type, Map labels) { + this.name = name; + this.description = description; + this.type = type.toString(); + this.labels = TypeConversion.convertMapToJsonString(labels); + } + + public static EntityV2 fromProto(EntityProto.Entity entityProto) { + EntitySpecV2 spec = entityProto.getSpec(); + + return new EntityV2( + spec.getName(), spec.getDescription(), spec.getValueType(), spec.getLabelsMap()); + } + + public EntityProto.Entity toProto() { + EntityMeta.Builder meta = + EntityMeta.newBuilder() + .setCreatedTimestamp( + Timestamp.newBuilder().setSeconds(super.getCreated().getTime() / 1000L)) + .setLastUpdatedTimestamp( + Timestamp.newBuilder().setSeconds(super.getLastUpdated().getTime() / 1000L)); + + EntitySpecV2.Builder spec = + EntitySpecV2.newBuilder() + .setName(getName()) + .setDescription(getDescription()) + .setValueType(ValueType.Enum.valueOf(getType())) + .putAllLabels(TypeConversion.convertJsonStringToMap(labels)); + + // Build Entity + EntityProto.Entity entity = EntityProto.Entity.newBuilder().setMeta(meta).setSpec(spec).build(); + return entity; + } + + /** + * Updates the existing entity from a proto. + * + * @param entityProto EntityProto with updated spec + * @param projectName Project namespace of Entity which is to be created/updated + */ + public void updateFromProto(EntityProto.Entity entityProto, String projectName) { + EntitySpecV2 spec = entityProto.getSpec(); + + // Validate no change to type + if (!spec.getValueType().equals(ValueType.Enum.valueOf(getType()))) { + throw new IllegalArgumentException( + String.format( + "You are attempting to change the type of this entity in %s project from %s to %s. This isn't allowed. Please create a new entity.", + projectName, getType(), spec.getValueType().toString())); + } + + // 2. Update description, labels + this.setDescription(spec.getDescription()); + this.setLabels(TypeConversion.convertMapToJsonString(spec.getLabelsMap())); + } + + /** + * Determine whether an entity has all the specified labels. + * + * @param labelsFilter labels contain key-value mapping for labels attached to the Entity + * @return boolean True if Entity contains all labels in the labelsFilter + */ + public boolean hasAllLabels(Map labelsFilter) { + Map LabelsMap = this.getLabelsMap(); + for (String key : labelsFilter.keySet()) { + if (!LabelsMap.containsKey(key) || !LabelsMap.get(key).equals(labelsFilter.get(key))) { + return false; + } + } + return true; + } + + public Map getLabelsMap() { + return TypeConversion.convertJsonStringToMap(this.getLabels()); + } +} diff --git a/core/src/main/java/feast/core/model/Project.java b/core/src/main/java/feast/core/model/Project.java index c55830c8248..8135289a009 100644 --- a/core/src/main/java/feast/core/model/Project.java +++ b/core/src/main/java/feast/core/model/Project.java @@ -52,6 +52,13 @@ public class Project { mappedBy = "project") private Set featureSets; + @OneToMany( + cascade = CascadeType.ALL, + fetch = FetchType.EAGER, + orphanRemoval = true, + mappedBy = "project") + private Set entities; + public Project() { super(); } @@ -59,6 +66,7 @@ public Project() { public Project(String name) { this.name = name; this.featureSets = new HashSet<>(); + this.entities = new HashSet<>(); } public void addFeatureSet(FeatureSet featureSet) { @@ -66,6 +74,11 @@ public void addFeatureSet(FeatureSet featureSet) { featureSets.add(featureSet); } + public void addEntity(EntityV2 entity) { + entity.setProject(this); + entities.add(entity); + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/core/src/main/java/feast/core/service/SpecService.java b/core/src/main/java/feast/core/service/SpecService.java index 7aec1e577a4..287cc15394a 100644 --- a/core/src/main/java/feast/core/service/SpecService.java +++ b/core/src/main/java/feast/core/service/SpecService.java @@ -21,17 +21,24 @@ import static feast.core.validators.Matchers.checkValidCharactersAllowAsterisk; import com.google.protobuf.InvalidProtocolBufferException; +import feast.core.dao.EntityRepository; import feast.core.dao.FeatureSetRepository; import feast.core.dao.ProjectRepository; import feast.core.dao.StoreRepository; import feast.core.exception.RegistrationException; import feast.core.exception.RetrievalException; import feast.core.model.*; +import feast.core.validators.EntityValidator; import feast.core.validators.FeatureSetValidator; +import feast.proto.core.CoreServiceProto.ApplyEntityResponse; import feast.proto.core.CoreServiceProto.ApplyFeatureSetResponse; import feast.proto.core.CoreServiceProto.ApplyFeatureSetResponse.Status; +import feast.proto.core.CoreServiceProto.GetEntityRequest; +import feast.proto.core.CoreServiceProto.GetEntityResponse; import feast.proto.core.CoreServiceProto.GetFeatureSetRequest; import feast.proto.core.CoreServiceProto.GetFeatureSetResponse; +import feast.proto.core.CoreServiceProto.ListEntitiesRequest; +import feast.proto.core.CoreServiceProto.ListEntitiesResponse; import feast.proto.core.CoreServiceProto.ListFeatureSetsRequest; import feast.proto.core.CoreServiceProto.ListFeatureSetsResponse; import feast.proto.core.CoreServiceProto.ListFeaturesRequest; @@ -43,6 +50,7 @@ import feast.proto.core.CoreServiceProto.UpdateFeatureSetStatusResponse; import feast.proto.core.CoreServiceProto.UpdateStoreRequest; import feast.proto.core.CoreServiceProto.UpdateStoreResponse; +import feast.proto.core.EntityProto; import feast.proto.core.FeatureSetProto; import feast.proto.core.FeatureSetProto.FeatureSetStatus; import feast.proto.core.SourceProto; @@ -65,6 +73,7 @@ @Service public class SpecService { + private final EntityRepository entityRepository; private final FeatureSetRepository featureSetRepository; private final ProjectRepository projectRepository; private final StoreRepository storeRepository; @@ -72,16 +81,53 @@ public class SpecService { @Autowired public SpecService( + EntityRepository entityRepository, FeatureSetRepository featureSetRepository, StoreRepository storeRepository, ProjectRepository projectRepository, Source defaultSource) { + this.entityRepository = entityRepository; this.featureSetRepository = featureSetRepository; this.storeRepository = storeRepository; this.projectRepository = projectRepository; this.defaultSource = defaultSource; } + /** + * Get an entity matching the entity name and set project. The entity name and project are + * required. If the project is omitted, the default would be used. + * + * @param request GetEntityRequest Request + * @return Returns a GetEntityResponse containing an entity + */ + public GetEntityResponse getEntity(GetEntityRequest request) { + String projectName = request.getProject(); + String entityName = request.getName(); + + if (entityName.isEmpty()) { + throw new IllegalArgumentException("No entity name provided"); + } + // Autofill default project if project is not specified + if (projectName.isEmpty()) { + projectName = Project.DEFAULT_NAME; + } + + checkValidCharacters(projectName, "project"); + checkValidCharacters(entityName, "entity"); + + EntityV2 entity = entityRepository.findEntityByNameAndProject_Name(entityName, projectName); + + if (entity == null) { + throw new RetrievalException( + String.format("Entity with name \"%s\" could not be found.", entityName)); + } + + // Build GetEntityResponse + GetEntityResponse response = GetEntityResponse.newBuilder().setEntity(entity.toProto()).build(); + + return response; + } + /** * 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 @@ -277,6 +323,52 @@ public ListFeaturesResponse listFeatures(ListFeaturesRequest.Filter filter) { } } + /** + * Return a list of entities matching the entity name, project and labels provided in the filter. + * All fields are required. Use '*' in entity name and project, and empty map in labels in order + * to return all entities 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. If the + * project name is omitted, the default project would be used. + * + *

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

The labels in the filter accepts a map. All entities which contain every provided label will + * be returned. + * + * @param filter Filter containing the desired entity name, project and labels + * @return ListEntitiesResponse with list of entities found matching the filter + */ + public ListEntitiesResponse listEntities(ListEntitiesRequest.Filter filter) { + String project = filter.getProject(); + Map labelsFilter = filter.getLabelsMap(); + + // Autofill default project if project not specified + if (project.isEmpty()) { + project = Project.DEFAULT_NAME; + } + + checkValidCharacters(project, "project"); + + List entities = entityRepository.findAllByProject_Name(project); + + ListEntitiesResponse.Builder response = ListEntitiesResponse.newBuilder(); + if (entities.size() > 0) { + entities = + entities.stream() + .filter(entity -> entity.hasAllLabels(labelsFilter)) + .collect(Collectors.toList()); + for (EntityV2 entity : entities) { + response.addEntities(entity.toProto()); + } + } + + return response.build(); + } + /** Update FeatureSet's status by given FeatureSetReference and new status */ public UpdateFeatureSetStatusResponse updateFeatureSetStatus( UpdateFeatureSetStatusRequest request) { @@ -324,6 +416,61 @@ public ListStoresResponse listStores(ListStoresRequest.Filter filter) { } } + /** + * Creates or updates an entity in the repository. + * + *

This function is idempotent. If no changes are detected in the incoming entity's schema, + * this method will return the existing entity stored in the repository. If project is not + * specified, the entity will be assigned to the 'default' project. + * + * @param newEntitySpec EntitySpecV2 that will be used to create or update an Entity. + * @param projectName Project namespace of Entity which is to be created/updated + */ + @Transactional + public ApplyEntityResponse applyEntity( + EntityProto.EntitySpecV2 newEntitySpec, String projectName) { + // Autofill default project if not specified + if (projectName == null || projectName.isEmpty()) { + projectName = Project.DEFAULT_NAME; + } + + // Validate incoming entity + EntityValidator.validateSpec(newEntitySpec); + + // Find project or create new one if it does not exist + Project project = projectRepository.findById(projectName).orElse(new Project(projectName)); + + // Ensure that the project retrieved from repository is not archived + if (project.isArchived()) { + throw new IllegalArgumentException(String.format("Project is archived: %s", projectName)); + } + + // Retrieve existing Entity + EntityV2 entity = + entityRepository.findEntityByNameAndProject_Name(newEntitySpec.getName(), projectName); + + EntityProto.Entity newEntity = EntityProto.Entity.newBuilder().setSpec(newEntitySpec).build(); + if (entity == null) { + // Create new entity since it doesn't exist + entity = EntityV2.fromProto(newEntity); + } else { + // If the entity remains unchanged, we do nothing. + if (entity.toProto().getSpec().equals(newEntitySpec)) { + return ApplyEntityResponse.newBuilder().setEntity(entity.toProto()).build(); + } + entity.updateFromProto(newEntity, projectName); + } + + // Persist the EntityV2 object + project.addEntity(entity); + projectRepository.saveAndFlush(project); + + // Build ApplyEntityResponse + ApplyEntityResponse response = + ApplyEntityResponse.newBuilder().setEntity(entity.toProto()).build(); + return response; + } + /** * Creates or updates a feature set in the repository. * diff --git a/core/src/main/java/feast/core/util/TypeConversion.java b/core/src/main/java/feast/core/util/TypeConversion.java index e6b7ef33cbc..bbdfa948049 100644 --- a/core/src/main/java/feast/core/util/TypeConversion.java +++ b/core/src/main/java/feast/core/util/TypeConversion.java @@ -18,6 +18,7 @@ import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import feast.proto.types.ValueProto.ValueType.Enum; import java.lang.reflect.Type; import java.util.*; @@ -61,6 +62,20 @@ public static Map convertJsonStringToMap(String jsonString) { return gson.fromJson(jsonString, stringMapType); } + /** + * Unmarshals a given json string to Enum map + * + * @param jsonString valid json formatted string + * @return map of keys to Enum values in json string + */ + public static Map convertJsonStringToEnumMap(String jsonString) { + if (jsonString == null || jsonString.equals("") || jsonString.equals("{}")) { + return Collections.emptyMap(); + } + Type stringMapType = new TypeToken>() {}.getType(); + return gson.fromJson(jsonString, stringMapType); + } + /** * Marshals a given map into its corresponding json string * @@ -70,4 +85,14 @@ public static Map convertJsonStringToMap(String jsonString) { public static String convertMapToJsonString(Map map) { return gson.toJson(map); } + + /** + * Marshals a given Enum map into its corresponding json string + * + * @param map + * @return json string corresponding to given Enum map + */ + public static String convertEnumMapToJsonString(Map map) { + return gson.toJson(map); + } } diff --git a/core/src/main/java/feast/core/validators/EntityValidator.java b/core/src/main/java/feast/core/validators/EntityValidator.java new file mode 100644 index 00000000000..743a0446902 --- /dev/null +++ b/core/src/main/java/feast/core/validators/EntityValidator.java @@ -0,0 +1,37 @@ +/* + * 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 static feast.core.validators.Matchers.checkValidCharacters; + +import feast.proto.core.EntityProto; + +public class EntityValidator { + public static void validateSpec(EntityProto.EntitySpecV2 entitySpec) { + if (entitySpec.getName().isEmpty()) { + throw new IllegalArgumentException("Entity name must be provided"); + } + if (entitySpec.getValueType().toString().isEmpty()) { + throw new IllegalArgumentException("Entity type must not be empty"); + } + if (entitySpec.getLabelsMap().containsKey("")) { + throw new IllegalArgumentException("Entity label keys must not be empty"); + } + + checkValidCharacters(entitySpec.getName(), "entity"); + } +} diff --git a/core/src/main/resources/db/migration/V2.7__Entities_Higher_Level_Concept.sql b/core/src/main/resources/db/migration/V2.7__Entities_Higher_Level_Concept.sql new file mode 100644 index 00000000000..183664ad658 --- /dev/null +++ b/core/src/main/resources/db/migration/V2.7__Entities_Higher_Level_Concept.sql @@ -0,0 +1,21 @@ +-- Migrating to Entities as a higher-level concept + +CREATE TABLE entities_v2( + id bigint NOT NULL, + created timestamp without time zone NOT NULL, + last_updated timestamp without time zone NOT NULL, + name character varying(255), + project_name character varying(255), + type character varying(255), + description text, + labels text +); + +ALTER TABLE ONLY entities_v2 + ADD CONSTRAINT entities_v2_pkey PRIMARY KEY (id); + +ALTER TABLE ONLY entities_v2 + ADD CONSTRAINT entities_v2_project_ukey UNIQUE (name, project_name); + +ALTER TABLE ONLY entities_v2 + ADD CONSTRAINT entities_v2_project_fkey FOREIGN KEY (project_name) REFERENCES projects(name); \ No newline at end of file diff --git a/core/src/test/java/feast/core/service/SpecServiceIT.java b/core/src/test/java/feast/core/service/SpecServiceIT.java index 697ad424d3e..11e76a4c543 100644 --- a/core/src/test/java/feast/core/service/SpecServiceIT.java +++ b/core/src/test/java/feast/core/service/SpecServiceIT.java @@ -68,6 +68,27 @@ public static void globalSetUp(@Value("${grpc.server.port}") int port) { public void initState() { SourceProto.Source source = DataGenerator.getDefaultSource(); + apiClient.simpleApplyEntity( + "default", + DataGenerator.createEntitySpecV2( + "entity1", + "Entity 1 description", + ValueProto.ValueType.Enum.STRING, + ImmutableMap.of("label_key", "label_value"))); + apiClient.simpleApplyEntity( + "default", + DataGenerator.createEntitySpecV2( + "entity2", + "Entity 2 description", + ValueProto.ValueType.Enum.STRING, + ImmutableMap.of("label_key2", "label_value2"))); + apiClient.simpleApplyEntity( + "project1", + DataGenerator.createEntitySpecV2( + "entity3", + "Entity 3 description", + ValueProto.ValueType.Enum.STRING, + ImmutableMap.of("label_key2", "label_value2"))); apiClient.simpleApplyFeatureSet( DataGenerator.createFeatureSet( source, @@ -88,7 +109,7 @@ public void initState() { "project1", "fs3", ImmutableList.of( - DataGenerator.createEntity("user_id", ValueProto.ValueType.Enum.STRING)), + DataGenerator.createEntitySpec("user_id", ValueProto.ValueType.Enum.STRING)), ImmutableList.of( DataGenerator.createFeature( "feature1", ValueProto.ValueType.Enum.INT32, Collections.emptyMap()), @@ -101,7 +122,7 @@ public void initState() { "project1", "fs4", ImmutableList.of( - DataGenerator.createEntity("customer_id", ValueProto.ValueType.Enum.STRING)), + DataGenerator.createEntitySpec("customer_id", ValueProto.ValueType.Enum.STRING)), ImmutableList.of( DataGenerator.createFeature( "feature2", @@ -114,7 +135,7 @@ public void initState() { "project1", "fs5", ImmutableList.of( - DataGenerator.createEntity("customer_id", ValueProto.ValueType.Enum.STRING)), + DataGenerator.createEntitySpec("customer_id", ValueProto.ValueType.Enum.STRING)), ImmutableList.of( DataGenerator.createFeature( "feature3", @@ -199,6 +220,51 @@ public void shouldThrowExceptionGivenMissingFeatureSetName() { } } + @Nested + class ListEntities { + @Test + public void shouldFilterEntitiesByLabels() { + List entities = + apiClient.simpleListEntities("", ImmutableMap.of("label_key2", "label_value2")); + + assertThat(entities, hasSize(1)); + assertThat(entities, hasItem(hasProperty("spec", hasProperty("name", equalTo("entity2"))))); + } + + @Test + public void shouldUseDefaultProjectIfProjectUnspecified() { + List entities = apiClient.simpleListEntities(""); + + assertThat(entities, hasSize(2)); + assertThat(entities, hasItem(hasProperty("spec", hasProperty("name", equalTo("entity1"))))); + } + + @Test + public void shouldFilterEntitiesByProjectAndLabels() { + List entities = + apiClient.simpleListEntities("project1", ImmutableMap.of("label_key2", "label_value2")); + + assertThat(entities, hasSize(1)); + assertThat(entities, hasItem(hasProperty("spec", hasProperty("name", equalTo("entity3"))))); + } + + @Test + public void shouldThrowExceptionGivenWildcardProject() { + CoreServiceProto.ListEntitiesRequest.Filter filter = + CoreServiceProto.ListEntitiesRequest.Filter.newBuilder().setProject("default*").build(); + StatusRuntimeException exc = + assertThrows(StatusRuntimeException.class, () -> apiClient.simpleListEntities(filter)); + + assertThat( + exc.getMessage(), + equalTo( + String.format( + "INVALID_ARGUMENT: invalid value for project resource, %s: " + + "argument must only contain alphanumeric characters and underscores.", + filter.getProject()))); + } + } + @Nested class ApplyFeatureSet { @Test @@ -547,7 +613,8 @@ public void shouldUpdateLabels() { "project1", "fs4", ImmutableList.of( - DataGenerator.createEntity("customer_id", ValueProto.ValueType.Enum.STRING)), + DataGenerator.createEntitySpec( + "customer_id", ValueProto.ValueType.Enum.STRING)), ImmutableList.of( DataGenerator.createFeature( "feature2", @@ -574,7 +641,8 @@ public void shouldAcceptFeatureSetLabels() { "", "some", ImmutableList.of( - DataGenerator.createEntity("customer_id", ValueProto.ValueType.Enum.STRING)), + DataGenerator.createEntitySpec( + "customer_id", ValueProto.ValueType.Enum.STRING)), ImmutableList.of(), ImmutableMap.of("label", "some"))); @@ -584,6 +652,139 @@ public void shouldAcceptFeatureSetLabels() { } } + @Nested + class ApplyEntity { + @Test + public void shouldThrowExceptionGivenEntityWithDash() { + StatusRuntimeException exc = + assertThrows( + StatusRuntimeException.class, + () -> + apiClient.simpleApplyEntity( + "default", + DataGenerator.createEntitySpecV2( + "dash-entity", + "Dash Entity description", + ValueProto.ValueType.Enum.STRING, + ImmutableMap.of("test_key", "test_value")))); + + assertThat( + exc.getMessage(), + equalTo( + String.format( + "INTERNAL: invalid value for %s resource, %s: %s", + "entity", + "dash-entity", + "argument must only contain alphanumeric characters and underscores."))); + } + + @Test + public void shouldThrowExceptionIfTypeChanged() { + String projectName = "default"; + + EntityProto.EntitySpecV2 spec = + DataGenerator.createEntitySpecV2( + "entity1", + "Entity description", + ValueProto.ValueType.Enum.FLOAT, + ImmutableMap.of("label_key", "label_value")); + + StatusRuntimeException exc = + assertThrows( + StatusRuntimeException.class, () -> apiClient.simpleApplyEntity("default", spec)); + + assertThat( + exc.getMessage(), + equalTo( + String.format( + "INTERNAL: You are attempting to change the type of this entity in %s project from %s to %s. This isn't allowed. Please create a new entity.", + "default", "STRING", spec.getValueType()))); + } + + @Test + public void shouldReturnEntityIfEntityHasNotChanged() { + String projectName = "default"; + EntityProto.EntitySpecV2 spec = apiClient.simpleGetEntity(projectName, "entity1").getSpec(); + + CoreServiceProto.ApplyEntityResponse response = + apiClient.simpleApplyEntity(projectName, spec); + + assertThat(response.getEntity().getSpec().getName(), equalTo(spec.getName())); + assertThat(response.getEntity().getSpec().getDescription(), equalTo(spec.getDescription())); + assertThat(response.getEntity().getSpec().getLabelsMap(), equalTo(spec.getLabelsMap())); + assertThat(response.getEntity().getSpec().getValueType(), equalTo(spec.getValueType())); + } + + @Test + public void shouldApplyEntityIfNotExists() { + String projectName = "default"; + EntityProto.EntitySpecV2 spec = + DataGenerator.createEntitySpecV2( + "new_entity", + "Entity description", + ValueProto.ValueType.Enum.STRING, + ImmutableMap.of("label_key", "label_value")); + + CoreServiceProto.ApplyEntityResponse response = + apiClient.simpleApplyEntity(projectName, spec); + + assertThat(response.getEntity().getSpec().getName(), equalTo(spec.getName())); + assertThat(response.getEntity().getSpec().getDescription(), equalTo(spec.getDescription())); + assertThat(response.getEntity().getSpec().getLabelsMap(), equalTo(spec.getLabelsMap())); + assertThat(response.getEntity().getSpec().getValueType(), equalTo(spec.getValueType())); + } + + @Test + public void shouldCreateProjectWhenNotAlreadyExists() { + EntityProto.EntitySpecV2 spec = + DataGenerator.createEntitySpecV2( + "new_entity2", + "Entity description", + ValueProto.ValueType.Enum.STRING, + ImmutableMap.of("key1", "val1")); + CoreServiceProto.ApplyEntityResponse response = + apiClient.simpleApplyEntity("new_project", spec); + + assertThat(response.getEntity().getSpec().getName(), equalTo(spec.getName())); + assertThat(response.getEntity().getSpec().getDescription(), equalTo(spec.getDescription())); + assertThat(response.getEntity().getSpec().getLabelsMap(), equalTo(spec.getLabelsMap())); + assertThat(response.getEntity().getSpec().getValueType(), equalTo(spec.getValueType())); + } + + @Test + public void shouldFailWhenProjectIsArchived() { + apiClient.createProject("archived"); + apiClient.archiveProject("archived"); + + StatusRuntimeException exc = + assertThrows( + StatusRuntimeException.class, + () -> + apiClient.simpleApplyEntity( + "archived", + DataGenerator.createEntitySpecV2( + "new_entity3", + "Entity description", + ValueProto.ValueType.Enum.STRING, + ImmutableMap.of("key1", "val1")))); + assertThat(exc.getMessage(), equalTo("INTERNAL: Project is archived: archived")); + } + + @Test + public void shouldUpdateLabels() { + EntityProto.EntitySpecV2 spec = + DataGenerator.createEntitySpecV2( + "entity1", + "Entity description", + ValueProto.ValueType.Enum.STRING, + ImmutableMap.of("label_key", "label_value", "label_key2", "label_value2")); + + CoreServiceProto.ApplyEntityResponse response = apiClient.simpleApplyEntity("default", spec); + + assertThat(response.getEntity().getSpec().getLabelsMap(), equalTo(spec.getLabelsMap())); + } + } + @Nested class UpdateStore { @Test @@ -624,6 +825,25 @@ public void shouldThrowExceptionGivenMissingFeatureSet() { } } + @Nested + class GetEntity { + @Test + public void shouldThrowExceptionGivenMissingEntity() { + StatusRuntimeException exc = + assertThrows( + StatusRuntimeException.class, () -> apiClient.simpleGetEntity("default", "")); + + assertThat(exc.getMessage(), equalTo("INVALID_ARGUMENT: No entity name provided")); + } + + public void shouldRetrieveFromDefaultIfProjectNotSpecified() { + String entityName = "entity1"; + EntityProto.Entity entity = apiClient.simpleGetEntity("", entityName); + + assertThat(entity.getSpec().getName(), equalTo(entityName)); + } + } + @Nested class ListStores { @Test diff --git a/go.mod b/go.mod index 8f9d12581ad..f640fc77534 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/golang/mock v1.2.0 github.com/golang/protobuf v1.4.2 - github.com/google/go-cmp v0.4.0 + github.com/google/go-cmp v0.5.0 github.com/huandu/xstrings v1.2.0 // indirect github.com/lyft/protoc-gen-validate v0.1.0 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect @@ -23,11 +23,11 @@ require ( github.com/woop/protoc-gen-doc v1.3.0 // indirect go.opencensus.io v0.22.3 // indirect golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect - golang.org/x/net v0.0.0-20200513185701-a91f0712d120 + golang.org/x/net v0.0.0-20200822124328-c89045814202 golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9 // indirect - golang.org/x/tools v0.0.0-20200622203043-20e05c1c8ffa // indirect + golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346 // indirect google.golang.org/grpc v1.29.1 - google.golang.org/protobuf v1.24.0 // indirect + google.golang.org/protobuf v1.25.0 // indirect gopkg.in/russross/blackfriday.v2 v2.0.0 // indirect gopkg.in/yaml.v2 v2.2.4 istio.io/gogo-genproto v0.0.0-20191212213402-78a529a42cd8 // indirect diff --git a/go.sum b/go.sum index 7038e78cbc0..afc554287de 100644 --- a/go.sum +++ b/go.sum @@ -165,6 +165,7 @@ 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/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -334,6 +335,7 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -356,6 +358,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -370,6 +373,7 @@ golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPI golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I= @@ -396,6 +400,7 @@ golang.org/x/net v0.0.0-20200320220750-118fecf932d8 h1:1+zQlQqEEhUeStBTi653GZAnA golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200513185701-a91f0712d120 h1:EZ3cVSzKOlJxAd8e8YAJ7no8nNypTxexh/YE/xW3ZEY= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -406,6 +411,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -474,10 +480,15 @@ golang.org/x/tools v0.0.0-20200604042327-9b20fe4cabe8 h1:8Xr1qwxn90MXYKftwNxIO2g golang.org/x/tools v0.0.0-20200604042327-9b20fe4cabe8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200622203043-20e05c1c8ffa h1:mMXQKlWCw9mIWgVLLfiycDZjMHMMYqiuakI4E/l2xcA= golang.org/x/tools v0.0.0-20200622203043-20e05c1c8ffa/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200923182640-463111b69878 h1:VUw1+Jf6KJPf82mbTQMia6HCnNMv2BbAipkEZ4KTcqQ= +golang.org/x/tools v0.0.0-20200923182640-463111b69878/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346 h1:hzJjkvxUIF3bSt+v8N5tBQNx/605vszZJ+3XsIamzZo= +golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= @@ -530,6 +541,8 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 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= diff --git a/protos/feast/core/CoreService.proto b/protos/feast/core/CoreService.proto index 59a50b823f0..a6ccd8ce598 100644 --- a/protos/feast/core/CoreService.proto +++ b/protos/feast/core/CoreService.proto @@ -23,6 +23,7 @@ option java_package = "feast.proto.core"; import "google/protobuf/timestamp.proto"; import "tensorflow_metadata/proto/v0/statistics.proto"; +import "feast/core/Entity.proto"; import "feast/core/FeatureSet.proto"; import "feast/core/Store.proto"; import "feast/core/FeatureSetReference.proto"; @@ -35,6 +36,9 @@ service CoreService { // Returns a specific feature set rpc GetFeatureSet (GetFeatureSetRequest) returns (GetFeatureSetResponse); + // Returns a specific entity + rpc GetEntity (GetEntityRequest) returns (GetEntityResponse); + // Retrieve feature set details given a filter. // // Returns all feature sets matching that filter. If none are found, @@ -71,6 +75,21 @@ service CoreService { // - Changes to feature name and type rpc ApplyFeatureSet (ApplyFeatureSetRequest) returns (ApplyFeatureSetResponse); + // Create or update and existing entity. + // + // This function is idempotent - it will not create a new entity if schema does not change. + // Schema changes will update the entity if the changes are valid. + // Following changes are not valid: + // - Changes to name + // - Changes to type + rpc ApplyEntity (ApplyEntityRequest) returns (ApplyEntityResponse); + + // Returns all entity references and respective entities matching that filter. If none are found + // an empty map will be returned + // If no filter is provided in the request, the response will contain all the entities + // currently stored in the default project. + rpc ListEntities (ListEntitiesRequest) returns (ListEntitiesResponse); + // Updates core with the configuration of the store. // // If the changes are valid, core will return the given store configuration in response, and @@ -166,6 +185,40 @@ message ListFeatureSetsResponse { repeated feast.core.FeatureSet feature_sets = 1; } +// Request for a single entity +message GetEntityRequest { + // Name of entity (required). + string name = 1; + + // Name of project the entity belongs to. If omitted will default to 'default' project. + string project = 2; +} + +// Response containing a single entity +message GetEntityResponse { + feast.core.Entity entity = 1; +} + +// Retrieves details for all versions of a specific entity +message ListEntitiesRequest { + Filter filter = 1; + + message Filter { + // Optional. Specifies the name of the project to list Entities in. + // 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; + + // Optional. User defined metadata for entity. + // Entities with all matching labels will be returned. + map labels = 4; + } +} + +message ListEntitiesResponse { + repeated feast.core.Entity entities = 1; +} + message ListFeaturesRequest { message Filter { // User defined metadata for feature. @@ -202,6 +255,19 @@ message ListStoresResponse { repeated feast.core.Store store = 1; } +message ApplyEntityRequest { + // If project is unspecified, will default to 'default' project. + // If project specified does not exist, the project would be automatically created. + feast.core.EntitySpecV2 spec = 1; + + // Name of project that this entity belongs to. + string project = 2; +} + +message ApplyEntityResponse { + feast.core.Entity entity = 1; +} + message ApplyFeatureSetRequest { // Feature set version // If project is unspecified, will default to 'default' project. diff --git a/protos/feast/core/Entity.proto b/protos/feast/core/Entity.proto new file mode 100644 index 00000000000..693100c64ab --- /dev/null +++ b/protos/feast/core/Entity.proto @@ -0,0 +1,50 @@ +// +// * 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 = "EntityProto"; +option go_package = "github.com/feast-dev/feast/sdk/go/protos/feast/core"; + +import "feast/types/Value.proto"; +import "google/protobuf/timestamp.proto"; + +message Entity { + // User-specified specifications of this entity. + EntitySpecV2 spec = 1; + // System-populated metadata for this entity. + EntityMeta meta = 2; +} + +message EntitySpecV2 { + // Name of the entity. + string name = 1; + + // Type of the entity. + feast.types.ValueType.Enum value_type = 2; + + // Description of the entity. + string description = 3; + + // User defined metadata + map labels = 8; +} + +message EntityMeta { + google.protobuf.Timestamp created_timestamp = 1; + google.protobuf.Timestamp last_updated_timestamp = 2; +} \ No newline at end of file diff --git a/sdk/go/protos/feast/core/CoreService.pb.go b/sdk/go/protos/feast/core/CoreService.pb.go index f0e35be844c..55a59f6d30e 100644 --- a/sdk/go/protos/feast/core/CoreService.pb.go +++ b/sdk/go/protos/feast/core/CoreService.pb.go @@ -16,8 +16,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.24.0 -// protoc v3.10.0 +// protoc-gen-go v1.25.0 +// protoc v3.12.4 // source: feast/core/CoreService.proto package core @@ -101,7 +101,7 @@ func (x ApplyFeatureSetResponse_Status) Number() protoreflect.EnumNumber { // Deprecated: Use ApplyFeatureSetResponse_Status.Descriptor instead. func (ApplyFeatureSetResponse_Status) EnumDescriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{9, 0} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{15, 0} } type UpdateStoreResponse_Status int32 @@ -149,7 +149,7 @@ func (x UpdateStoreResponse_Status) Number() protoreflect.EnumNumber { // Deprecated: Use UpdateStoreResponse_Status.Descriptor instead. func (UpdateStoreResponse_Status) EnumDescriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{13, 0} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{19, 0} } // Request for a single feature set @@ -353,6 +353,207 @@ func (x *ListFeatureSetsResponse) GetFeatureSets() []*FeatureSet { return nil } +// Request for a single entity +type GetEntityRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Name of entity (required). + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Name of project the entity belongs to. If omitted will default to 'default' project. + Project string `protobuf:"bytes,2,opt,name=project,proto3" json:"project,omitempty"` +} + +func (x *GetEntityRequest) Reset() { + *x = GetEntityRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetEntityRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetEntityRequest) ProtoMessage() {} + +func (x *GetEntityRequest) 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) +} + +// Deprecated: Use GetEntityRequest.ProtoReflect.Descriptor instead. +func (*GetEntityRequest) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{4} +} + +func (x *GetEntityRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *GetEntityRequest) GetProject() string { + if x != nil { + return x.Project + } + return "" +} + +// Response containing a single entity +type GetEntityResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Entity *Entity `protobuf:"bytes,1,opt,name=entity,proto3" json:"entity,omitempty"` +} + +func (x *GetEntityResponse) Reset() { + *x = GetEntityResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetEntityResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetEntityResponse) ProtoMessage() {} + +func (x *GetEntityResponse) 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 mi.MessageOf(x) +} + +// Deprecated: Use GetEntityResponse.ProtoReflect.Descriptor instead. +func (*GetEntityResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{5} +} + +func (x *GetEntityResponse) GetEntity() *Entity { + if x != nil { + return x.Entity + } + return nil +} + +// Retrieves details for all versions of a specific entity +type ListEntitiesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Filter *ListEntitiesRequest_Filter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` +} + +func (x *ListEntitiesRequest) Reset() { + *x = ListEntitiesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListEntitiesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListEntitiesRequest) ProtoMessage() {} + +func (x *ListEntitiesRequest) 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) +} + +// Deprecated: Use ListEntitiesRequest.ProtoReflect.Descriptor instead. +func (*ListEntitiesRequest) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{6} +} + +func (x *ListEntitiesRequest) GetFilter() *ListEntitiesRequest_Filter { + if x != nil { + return x.Filter + } + return nil +} + +type ListEntitiesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Entities []*Entity `protobuf:"bytes,1,rep,name=entities,proto3" json:"entities,omitempty"` +} + +func (x *ListEntitiesResponse) Reset() { + *x = ListEntitiesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListEntitiesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListEntitiesResponse) ProtoMessage() {} + +func (x *ListEntitiesResponse) 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) +} + +// Deprecated: Use ListEntitiesResponse.ProtoReflect.Descriptor instead. +func (*ListEntitiesResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{7} +} + +func (x *ListEntitiesResponse) GetEntities() []*Entity { + if x != nil { + return x.Entities + } + return nil +} + type ListFeaturesRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -364,7 +565,7 @@ type ListFeaturesRequest struct { func (x *ListFeaturesRequest) Reset() { *x = ListFeaturesRequest{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[4] + mi := &file_feast_core_CoreService_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -377,7 +578,7 @@ func (x *ListFeaturesRequest) String() string { func (*ListFeaturesRequest) ProtoMessage() {} func (x *ListFeaturesRequest) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[4] + mi := &file_feast_core_CoreService_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -390,7 +591,7 @@ func (x *ListFeaturesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListFeaturesRequest.ProtoReflect.Descriptor instead. func (*ListFeaturesRequest) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{4} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{8} } func (x *ListFeaturesRequest) GetFilter() *ListFeaturesRequest_Filter { @@ -411,7 +612,7 @@ type ListFeaturesResponse struct { func (x *ListFeaturesResponse) Reset() { *x = ListFeaturesResponse{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[5] + mi := &file_feast_core_CoreService_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -424,7 +625,7 @@ func (x *ListFeaturesResponse) String() string { func (*ListFeaturesResponse) ProtoMessage() {} func (x *ListFeaturesResponse) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[5] + mi := &file_feast_core_CoreService_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -437,7 +638,7 @@ func (x *ListFeaturesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListFeaturesResponse.ProtoReflect.Descriptor instead. func (*ListFeaturesResponse) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{5} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{9} } func (x *ListFeaturesResponse) GetFeatures() map[string]*FeatureSpec { @@ -458,7 +659,7 @@ type ListStoresRequest struct { func (x *ListStoresRequest) Reset() { *x = ListStoresRequest{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[6] + mi := &file_feast_core_CoreService_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -471,7 +672,7 @@ func (x *ListStoresRequest) String() string { func (*ListStoresRequest) ProtoMessage() {} func (x *ListStoresRequest) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[6] + mi := &file_feast_core_CoreService_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -484,7 +685,7 @@ func (x *ListStoresRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListStoresRequest.ProtoReflect.Descriptor instead. func (*ListStoresRequest) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{6} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{10} } func (x *ListStoresRequest) GetFilter() *ListStoresRequest_Filter { @@ -505,7 +706,7 @@ type ListStoresResponse struct { func (x *ListStoresResponse) Reset() { *x = ListStoresResponse{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[7] + mi := &file_feast_core_CoreService_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -518,7 +719,7 @@ func (x *ListStoresResponse) String() string { func (*ListStoresResponse) ProtoMessage() {} func (x *ListStoresResponse) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[7] + mi := &file_feast_core_CoreService_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -531,7 +732,7 @@ func (x *ListStoresResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListStoresResponse.ProtoReflect.Descriptor instead. func (*ListStoresResponse) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{7} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{11} } func (x *ListStoresResponse) GetStore() []*Store { @@ -541,6 +742,111 @@ func (x *ListStoresResponse) GetStore() []*Store { return nil } +type ApplyEntityRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // If project is unspecified, will default to 'default' project. + // If project specified does not exist, the project would be automatically created. + Spec *EntitySpecV2 `protobuf:"bytes,1,opt,name=spec,proto3" json:"spec,omitempty"` + // Name of project that this entity belongs to. + Project string `protobuf:"bytes,2,opt,name=project,proto3" json:"project,omitempty"` +} + +func (x *ApplyEntityRequest) Reset() { + *x = ApplyEntityRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ApplyEntityRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ApplyEntityRequest) ProtoMessage() {} + +func (x *ApplyEntityRequest) 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) +} + +// Deprecated: Use ApplyEntityRequest.ProtoReflect.Descriptor instead. +func (*ApplyEntityRequest) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{12} +} + +func (x *ApplyEntityRequest) GetSpec() *EntitySpecV2 { + if x != nil { + return x.Spec + } + return nil +} + +func (x *ApplyEntityRequest) GetProject() string { + if x != nil { + return x.Project + } + return "" +} + +type ApplyEntityResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Entity *Entity `protobuf:"bytes,1,opt,name=entity,proto3" json:"entity,omitempty"` +} + +func (x *ApplyEntityResponse) Reset() { + *x = ApplyEntityResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ApplyEntityResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ApplyEntityResponse) ProtoMessage() {} + +func (x *ApplyEntityResponse) 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) +} + +// Deprecated: Use ApplyEntityResponse.ProtoReflect.Descriptor instead. +func (*ApplyEntityResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{13} +} + +func (x *ApplyEntityResponse) GetEntity() *Entity { + if x != nil { + return x.Entity + } + return nil +} + type ApplyFeatureSetRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -555,7 +861,7 @@ type ApplyFeatureSetRequest struct { func (x *ApplyFeatureSetRequest) Reset() { *x = ApplyFeatureSetRequest{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[8] + mi := &file_feast_core_CoreService_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -568,7 +874,7 @@ func (x *ApplyFeatureSetRequest) String() string { func (*ApplyFeatureSetRequest) ProtoMessage() {} func (x *ApplyFeatureSetRequest) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[8] + mi := &file_feast_core_CoreService_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -581,7 +887,7 @@ func (x *ApplyFeatureSetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyFeatureSetRequest.ProtoReflect.Descriptor instead. func (*ApplyFeatureSetRequest) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{8} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{14} } func (x *ApplyFeatureSetRequest) GetFeatureSet() *FeatureSet { @@ -603,7 +909,7 @@ type ApplyFeatureSetResponse struct { func (x *ApplyFeatureSetResponse) Reset() { *x = ApplyFeatureSetResponse{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[9] + mi := &file_feast_core_CoreService_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -616,7 +922,7 @@ func (x *ApplyFeatureSetResponse) String() string { func (*ApplyFeatureSetResponse) ProtoMessage() {} func (x *ApplyFeatureSetResponse) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[9] + mi := &file_feast_core_CoreService_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -629,7 +935,7 @@ func (x *ApplyFeatureSetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyFeatureSetResponse.ProtoReflect.Descriptor instead. func (*ApplyFeatureSetResponse) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{9} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{15} } func (x *ApplyFeatureSetResponse) GetFeatureSet() *FeatureSet { @@ -655,7 +961,7 @@ type GetFeastCoreVersionRequest struct { func (x *GetFeastCoreVersionRequest) Reset() { *x = GetFeastCoreVersionRequest{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[10] + mi := &file_feast_core_CoreService_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -668,7 +974,7 @@ func (x *GetFeastCoreVersionRequest) String() string { func (*GetFeastCoreVersionRequest) ProtoMessage() {} func (x *GetFeastCoreVersionRequest) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[10] + mi := &file_feast_core_CoreService_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -681,7 +987,7 @@ func (x *GetFeastCoreVersionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetFeastCoreVersionRequest.ProtoReflect.Descriptor instead. func (*GetFeastCoreVersionRequest) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{10} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{16} } type GetFeastCoreVersionResponse struct { @@ -695,7 +1001,7 @@ type GetFeastCoreVersionResponse struct { func (x *GetFeastCoreVersionResponse) Reset() { *x = GetFeastCoreVersionResponse{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[11] + mi := &file_feast_core_CoreService_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -708,7 +1014,7 @@ func (x *GetFeastCoreVersionResponse) String() string { func (*GetFeastCoreVersionResponse) ProtoMessage() {} func (x *GetFeastCoreVersionResponse) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[11] + mi := &file_feast_core_CoreService_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -721,7 +1027,7 @@ func (x *GetFeastCoreVersionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetFeastCoreVersionResponse.ProtoReflect.Descriptor instead. func (*GetFeastCoreVersionResponse) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{11} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{17} } func (x *GetFeastCoreVersionResponse) GetVersion() string { @@ -742,7 +1048,7 @@ type UpdateStoreRequest struct { func (x *UpdateStoreRequest) Reset() { *x = UpdateStoreRequest{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[12] + mi := &file_feast_core_CoreService_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -755,7 +1061,7 @@ func (x *UpdateStoreRequest) String() string { func (*UpdateStoreRequest) ProtoMessage() {} func (x *UpdateStoreRequest) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[12] + mi := &file_feast_core_CoreService_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -768,7 +1074,7 @@ func (x *UpdateStoreRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateStoreRequest.ProtoReflect.Descriptor instead. func (*UpdateStoreRequest) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{12} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{18} } func (x *UpdateStoreRequest) GetStore() *Store { @@ -790,7 +1096,7 @@ type UpdateStoreResponse struct { func (x *UpdateStoreResponse) Reset() { *x = UpdateStoreResponse{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[13] + mi := &file_feast_core_CoreService_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -803,7 +1109,7 @@ func (x *UpdateStoreResponse) String() string { func (*UpdateStoreResponse) ProtoMessage() {} func (x *UpdateStoreResponse) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[13] + mi := &file_feast_core_CoreService_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -816,7 +1122,7 @@ func (x *UpdateStoreResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateStoreResponse.ProtoReflect.Descriptor instead. func (*UpdateStoreResponse) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{13} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{19} } func (x *UpdateStoreResponse) GetStore() *Store { @@ -846,7 +1152,7 @@ type CreateProjectRequest struct { func (x *CreateProjectRequest) Reset() { *x = CreateProjectRequest{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[14] + mi := &file_feast_core_CoreService_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -859,7 +1165,7 @@ func (x *CreateProjectRequest) String() string { func (*CreateProjectRequest) ProtoMessage() {} func (x *CreateProjectRequest) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[14] + mi := &file_feast_core_CoreService_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -872,7 +1178,7 @@ func (x *CreateProjectRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateProjectRequest.ProtoReflect.Descriptor instead. func (*CreateProjectRequest) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{14} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{20} } func (x *CreateProjectRequest) GetName() string { @@ -892,7 +1198,7 @@ type CreateProjectResponse struct { func (x *CreateProjectResponse) Reset() { *x = CreateProjectResponse{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[15] + mi := &file_feast_core_CoreService_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -905,7 +1211,7 @@ func (x *CreateProjectResponse) String() string { func (*CreateProjectResponse) ProtoMessage() {} func (x *CreateProjectResponse) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[15] + mi := &file_feast_core_CoreService_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -918,7 +1224,7 @@ func (x *CreateProjectResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateProjectResponse.ProtoReflect.Descriptor instead. func (*CreateProjectResponse) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{15} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{21} } // Request for the archival of a project @@ -934,7 +1240,7 @@ type ArchiveProjectRequest struct { func (x *ArchiveProjectRequest) Reset() { *x = ArchiveProjectRequest{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[16] + mi := &file_feast_core_CoreService_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -947,7 +1253,7 @@ func (x *ArchiveProjectRequest) String() string { func (*ArchiveProjectRequest) ProtoMessage() {} func (x *ArchiveProjectRequest) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[16] + mi := &file_feast_core_CoreService_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -960,7 +1266,7 @@ func (x *ArchiveProjectRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ArchiveProjectRequest.ProtoReflect.Descriptor instead. func (*ArchiveProjectRequest) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{16} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{22} } func (x *ArchiveProjectRequest) GetName() string { @@ -980,7 +1286,7 @@ type ArchiveProjectResponse struct { func (x *ArchiveProjectResponse) Reset() { *x = ArchiveProjectResponse{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[17] + mi := &file_feast_core_CoreService_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -993,7 +1299,7 @@ func (x *ArchiveProjectResponse) String() string { func (*ArchiveProjectResponse) ProtoMessage() {} func (x *ArchiveProjectResponse) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[17] + mi := &file_feast_core_CoreService_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1006,7 +1312,7 @@ func (x *ArchiveProjectResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ArchiveProjectResponse.ProtoReflect.Descriptor instead. func (*ArchiveProjectResponse) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{17} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{23} } // Request for listing of projects @@ -1019,7 +1325,7 @@ type ListProjectsRequest struct { func (x *ListProjectsRequest) Reset() { *x = ListProjectsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[18] + mi := &file_feast_core_CoreService_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1032,7 +1338,7 @@ func (x *ListProjectsRequest) String() string { func (*ListProjectsRequest) ProtoMessage() {} func (x *ListProjectsRequest) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[18] + mi := &file_feast_core_CoreService_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1045,7 +1351,7 @@ func (x *ListProjectsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListProjectsRequest.ProtoReflect.Descriptor instead. func (*ListProjectsRequest) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{18} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{24} } // Response for listing of projects @@ -1061,7 +1367,7 @@ type ListProjectsResponse struct { func (x *ListProjectsResponse) Reset() { *x = ListProjectsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[19] + mi := &file_feast_core_CoreService_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1074,7 +1380,7 @@ func (x *ListProjectsResponse) String() string { func (*ListProjectsResponse) ProtoMessage() {} func (x *ListProjectsResponse) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[19] + mi := &file_feast_core_CoreService_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1087,7 +1393,7 @@ func (x *ListProjectsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListProjectsResponse.ProtoReflect.Descriptor instead. func (*ListProjectsResponse) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{19} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{25} } func (x *ListProjectsResponse) GetProjects() []string { @@ -1109,7 +1415,7 @@ type ListIngestionJobsRequest struct { func (x *ListIngestionJobsRequest) Reset() { *x = ListIngestionJobsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[20] + mi := &file_feast_core_CoreService_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1122,7 +1428,7 @@ func (x *ListIngestionJobsRequest) String() string { func (*ListIngestionJobsRequest) ProtoMessage() {} func (x *ListIngestionJobsRequest) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[20] + mi := &file_feast_core_CoreService_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1135,7 +1441,7 @@ func (x *ListIngestionJobsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListIngestionJobsRequest.ProtoReflect.Descriptor instead. func (*ListIngestionJobsRequest) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{20} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{26} } func (x *ListIngestionJobsRequest) GetFilter() *ListIngestionJobsRequest_Filter { @@ -1157,7 +1463,7 @@ type ListIngestionJobsResponse struct { func (x *ListIngestionJobsResponse) Reset() { *x = ListIngestionJobsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[21] + mi := &file_feast_core_CoreService_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1170,7 +1476,7 @@ func (x *ListIngestionJobsResponse) String() string { func (*ListIngestionJobsResponse) ProtoMessage() {} func (x *ListIngestionJobsResponse) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[21] + mi := &file_feast_core_CoreService_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1183,7 +1489,7 @@ func (x *ListIngestionJobsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListIngestionJobsResponse.ProtoReflect.Descriptor instead. func (*ListIngestionJobsResponse) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{21} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{27} } func (x *ListIngestionJobsResponse) GetJobs() []*IngestionJob { @@ -1206,7 +1512,7 @@ type RestartIngestionJobRequest struct { func (x *RestartIngestionJobRequest) Reset() { *x = RestartIngestionJobRequest{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[22] + mi := &file_feast_core_CoreService_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1219,7 +1525,7 @@ func (x *RestartIngestionJobRequest) String() string { func (*RestartIngestionJobRequest) ProtoMessage() {} func (x *RestartIngestionJobRequest) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[22] + mi := &file_feast_core_CoreService_proto_msgTypes[28] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1232,7 +1538,7 @@ func (x *RestartIngestionJobRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RestartIngestionJobRequest.ProtoReflect.Descriptor instead. func (*RestartIngestionJobRequest) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{22} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{28} } func (x *RestartIngestionJobRequest) GetId() string { @@ -1252,7 +1558,7 @@ type RestartIngestionJobResponse struct { func (x *RestartIngestionJobResponse) Reset() { *x = RestartIngestionJobResponse{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[23] + mi := &file_feast_core_CoreService_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1265,7 +1571,7 @@ func (x *RestartIngestionJobResponse) String() string { func (*RestartIngestionJobResponse) ProtoMessage() {} func (x *RestartIngestionJobResponse) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[23] + mi := &file_feast_core_CoreService_proto_msgTypes[29] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1278,7 +1584,7 @@ func (x *RestartIngestionJobResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RestartIngestionJobResponse.ProtoReflect.Descriptor instead. func (*RestartIngestionJobResponse) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{23} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{29} } // Request to stop ingestion job @@ -1294,7 +1600,7 @@ type StopIngestionJobRequest struct { func (x *StopIngestionJobRequest) Reset() { *x = StopIngestionJobRequest{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[24] + mi := &file_feast_core_CoreService_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1307,7 +1613,7 @@ func (x *StopIngestionJobRequest) String() string { func (*StopIngestionJobRequest) ProtoMessage() {} func (x *StopIngestionJobRequest) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[24] + mi := &file_feast_core_CoreService_proto_msgTypes[30] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1320,7 +1626,7 @@ func (x *StopIngestionJobRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use StopIngestionJobRequest.ProtoReflect.Descriptor instead. func (*StopIngestionJobRequest) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{24} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{30} } func (x *StopIngestionJobRequest) GetId() string { @@ -1340,7 +1646,7 @@ type StopIngestionJobResponse struct { func (x *StopIngestionJobResponse) Reset() { *x = StopIngestionJobResponse{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[25] + mi := &file_feast_core_CoreService_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1353,7 +1659,7 @@ func (x *StopIngestionJobResponse) String() string { func (*StopIngestionJobResponse) ProtoMessage() {} func (x *StopIngestionJobResponse) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[25] + mi := &file_feast_core_CoreService_proto_msgTypes[31] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1366,7 +1672,7 @@ func (x *StopIngestionJobResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use StopIngestionJobResponse.ProtoReflect.Descriptor instead. func (*StopIngestionJobResponse) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{25} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{31} } type GetFeatureStatisticsRequest struct { @@ -1403,7 +1709,7 @@ type GetFeatureStatisticsRequest struct { func (x *GetFeatureStatisticsRequest) Reset() { *x = GetFeatureStatisticsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[26] + mi := &file_feast_core_CoreService_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1416,7 +1722,7 @@ func (x *GetFeatureStatisticsRequest) String() string { func (*GetFeatureStatisticsRequest) ProtoMessage() {} func (x *GetFeatureStatisticsRequest) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[26] + mi := &file_feast_core_CoreService_proto_msgTypes[32] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1429,7 +1735,7 @@ func (x *GetFeatureStatisticsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetFeatureStatisticsRequest.ProtoReflect.Descriptor instead. func (*GetFeatureStatisticsRequest) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{26} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{32} } func (x *GetFeatureStatisticsRequest) GetFeatureSetId() string { @@ -1467,48 +1773,150 @@ func (x *GetFeatureStatisticsRequest) GetEndDate() *timestamp.Timestamp { return nil } -func (x *GetFeatureStatisticsRequest) GetIngestionIds() []string { +func (x *GetFeatureStatisticsRequest) GetIngestionIds() []string { + if x != nil { + return x.IngestionIds + } + return nil +} + +func (x *GetFeatureStatisticsRequest) GetForceRefresh() bool { + if x != nil { + return x.ForceRefresh + } + return false +} + +type GetFeatureStatisticsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Contains statistics for the requested data. + // Due to the limitations of TFDV and Facets, only a single dataset can be returned in, + // despite the message being of list type. + DatasetFeatureStatisticsList *v0.DatasetFeatureStatisticsList `protobuf:"bytes,1,opt,name=dataset_feature_statistics_list,json=datasetFeatureStatisticsList,proto3" json:"dataset_feature_statistics_list,omitempty"` +} + +func (x *GetFeatureStatisticsResponse) Reset() { + *x = GetFeatureStatisticsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetFeatureStatisticsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetFeatureStatisticsResponse) ProtoMessage() {} + +func (x *GetFeatureStatisticsResponse) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_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 GetFeatureStatisticsResponse.ProtoReflect.Descriptor instead. +func (*GetFeatureStatisticsResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{33} +} + +func (x *GetFeatureStatisticsResponse) GetDatasetFeatureStatisticsList() *v0.DatasetFeatureStatisticsList { + if x != nil { + return x.DatasetFeatureStatisticsList + } + return nil +} + +type UpdateFeatureSetStatusRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // FeatureSetReference of FeatureSet to update + Reference *FeatureSetReference `protobuf:"bytes,1,opt,name=reference,proto3" json:"reference,omitempty"` + // Target status + Status FeatureSetStatus `protobuf:"varint,2,opt,name=status,proto3,enum=feast.core.FeatureSetStatus" json:"status,omitempty"` +} + +func (x *UpdateFeatureSetStatusRequest) Reset() { + *x = UpdateFeatureSetStatusRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateFeatureSetStatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateFeatureSetStatusRequest) ProtoMessage() {} + +func (x *UpdateFeatureSetStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_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 UpdateFeatureSetStatusRequest.ProtoReflect.Descriptor instead. +func (*UpdateFeatureSetStatusRequest) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{34} +} + +func (x *UpdateFeatureSetStatusRequest) GetReference() *FeatureSetReference { if x != nil { - return x.IngestionIds + return x.Reference } return nil } -func (x *GetFeatureStatisticsRequest) GetForceRefresh() bool { +func (x *UpdateFeatureSetStatusRequest) GetStatus() FeatureSetStatus { if x != nil { - return x.ForceRefresh + return x.Status } - return false + return FeatureSetStatus_STATUS_INVALID } -type GetFeatureStatisticsResponse struct { +type UpdateFeatureSetStatusResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - - // Contains statistics for the requested data. - // Due to the limitations of TFDV and Facets, only a single dataset can be returned in, - // despite the message being of list type. - DatasetFeatureStatisticsList *v0.DatasetFeatureStatisticsList `protobuf:"bytes,1,opt,name=dataset_feature_statistics_list,json=datasetFeatureStatisticsList,proto3" json:"dataset_feature_statistics_list,omitempty"` } -func (x *GetFeatureStatisticsResponse) Reset() { - *x = GetFeatureStatisticsResponse{} +func (x *UpdateFeatureSetStatusResponse) Reset() { + *x = UpdateFeatureSetStatusResponse{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[27] + mi := &file_feast_core_CoreService_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } -func (x *GetFeatureStatisticsResponse) String() string { +func (x *UpdateFeatureSetStatusResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetFeatureStatisticsResponse) ProtoMessage() {} +func (*UpdateFeatureSetStatusResponse) ProtoMessage() {} -func (x *GetFeatureStatisticsResponse) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[27] +func (x *UpdateFeatureSetStatusResponse) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[35] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1519,16 +1927,9 @@ func (x *GetFeatureStatisticsResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetFeatureStatisticsResponse.ProtoReflect.Descriptor instead. -func (*GetFeatureStatisticsResponse) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{27} -} - -func (x *GetFeatureStatisticsResponse) GetDatasetFeatureStatisticsList() *v0.DatasetFeatureStatisticsList { - if x != nil { - return x.DatasetFeatureStatisticsList - } - return nil +// Deprecated: Use UpdateFeatureSetStatusResponse.ProtoReflect.Descriptor instead. +func (*UpdateFeatureSetStatusResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{35} } type ListFeatureSetsRequest_Filter struct { @@ -1555,12 +1956,15 @@ type ListFeatureSetsRequest_Filter struct { // User defined metadata for feature set. // Feature sets with all matching labels will be returned. Labels map[string]string `protobuf:"bytes,4,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // Filter by FeatureSet's current status + // Project and Feature Set name still must be specified (could be "*") + Status FeatureSetStatus `protobuf:"varint,5,opt,name=status,proto3,enum=feast.core.FeatureSetStatus" json:"status,omitempty"` } func (x *ListFeatureSetsRequest_Filter) Reset() { *x = ListFeatureSetsRequest_Filter{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[28] + mi := &file_feast_core_CoreService_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1573,7 +1977,7 @@ func (x *ListFeatureSetsRequest_Filter) String() string { func (*ListFeatureSetsRequest_Filter) ProtoMessage() {} func (x *ListFeatureSetsRequest_Filter) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[28] + mi := &file_feast_core_CoreService_proto_msgTypes[36] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1610,6 +2014,73 @@ func (x *ListFeatureSetsRequest_Filter) GetLabels() map[string]string { return nil } +func (x *ListFeatureSetsRequest_Filter) GetStatus() FeatureSetStatus { + if x != nil { + return x.Status + } + return FeatureSetStatus_STATUS_INVALID +} + +type ListEntitiesRequest_Filter struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Optional. Specifies the name of the project to list Entities in. + // 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"` + // Optional. User defined metadata for entity. + // Entities with all matching labels will be returned. + Labels map[string]string `protobuf:"bytes,4,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *ListEntitiesRequest_Filter) Reset() { + *x = ListEntitiesRequest_Filter{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[38] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListEntitiesRequest_Filter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListEntitiesRequest_Filter) ProtoMessage() {} + +func (x *ListEntitiesRequest_Filter) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[38] + 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 ListEntitiesRequest_Filter.ProtoReflect.Descriptor instead. +func (*ListEntitiesRequest_Filter) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{6, 0} +} + +func (x *ListEntitiesRequest_Filter) GetProject() string { + if x != nil { + return x.Project + } + return "" +} + +func (x *ListEntitiesRequest_Filter) GetLabels() map[string]string { + if x != nil { + return x.Labels + } + return nil +} + type ListFeaturesRequest_Filter struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1630,7 +2101,7 @@ type ListFeaturesRequest_Filter struct { func (x *ListFeaturesRequest_Filter) Reset() { *x = ListFeaturesRequest_Filter{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[30] + mi := &file_feast_core_CoreService_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1643,7 +2114,7 @@ func (x *ListFeaturesRequest_Filter) String() string { func (*ListFeaturesRequest_Filter) ProtoMessage() {} func (x *ListFeaturesRequest_Filter) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[30] + mi := &file_feast_core_CoreService_proto_msgTypes[40] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1656,7 +2127,7 @@ func (x *ListFeaturesRequest_Filter) ProtoReflect() protoreflect.Message { // Deprecated: Use ListFeaturesRequest_Filter.ProtoReflect.Descriptor instead. func (*ListFeaturesRequest_Filter) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{4, 0} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{8, 0} } func (x *ListFeaturesRequest_Filter) GetLabels() map[string]string { @@ -1692,7 +2163,7 @@ type ListStoresRequest_Filter struct { func (x *ListStoresRequest_Filter) Reset() { *x = ListStoresRequest_Filter{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[33] + mi := &file_feast_core_CoreService_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1705,7 +2176,7 @@ func (x *ListStoresRequest_Filter) String() string { func (*ListStoresRequest_Filter) ProtoMessage() {} func (x *ListStoresRequest_Filter) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[33] + mi := &file_feast_core_CoreService_proto_msgTypes[43] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1718,7 +2189,7 @@ func (x *ListStoresRequest_Filter) ProtoReflect() protoreflect.Message { // Deprecated: Use ListStoresRequest_Filter.ProtoReflect.Descriptor instead. func (*ListStoresRequest_Filter) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{6, 0} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{10, 0} } func (x *ListStoresRequest_Filter) GetName() string { @@ -1744,7 +2215,7 @@ type ListIngestionJobsRequest_Filter struct { func (x *ListIngestionJobsRequest_Filter) Reset() { *x = ListIngestionJobsRequest_Filter{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[34] + mi := &file_feast_core_CoreService_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1757,7 +2228,7 @@ func (x *ListIngestionJobsRequest_Filter) String() string { func (*ListIngestionJobsRequest_Filter) ProtoMessage() {} func (x *ListIngestionJobsRequest_Filter) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[34] + mi := &file_feast_core_CoreService_proto_msgTypes[44] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1770,7 +2241,7 @@ func (x *ListIngestionJobsRequest_Filter) ProtoReflect() protoreflect.Message { // Deprecated: Use ListIngestionJobsRequest_Filter.ProtoReflect.Descriptor instead. func (*ListIngestionJobsRequest_Filter) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{20, 0} + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{26, 0} } func (x *ListIngestionJobsRequest_Filter) GetId() string { @@ -1804,287 +2275,366 @@ var file_feast_core_CoreService_proto_rawDesc = []byte{ 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2d, 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, 0x74, 0x61, 0x74, 0x69, 0x73, - 0x74, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 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, 0xb4, 0x02, 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, 0xd6, 0x01, 0x0a, 0x06, 0x46, - 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, + 0x74, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x66, 0x65, 0x61, 0x73, + 0x74, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 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, - 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, 0x12, 0x4d, 0x0a, 0x06, 0x6c, 0x61, 0x62, - 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 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, 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, 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, 0x9d, 0x02, 0x0a, 0x13, 0x4c, 0x69, - 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x3e, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x26, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 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, 0xc5, 0x01, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x4a, 0x0a, 0x06, - 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x66, - 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, - 0x6c, 0x74, 0x65, 0x72, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x6e, 0x74, 0x69, - 0x74, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x65, 0x6e, 0x74, 0x69, - 0x74, 0x69, 0x65, 0x73, 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, 0x1a, 0x39, - 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 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, 0xea, 0x02, 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, 0x8c, 0x02, 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, 0x12, 0x4d, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x35, 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, 0x2e, 0x4c, 0x61, + 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x73, 0x12, 0x34, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 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, 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, 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, 0x40, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x45, + 0x6e, 0x74, 0x69, 0x74, 0x79, 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, + 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x3f, 0x0a, 0x11, 0x47, 0x65, + 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x2a, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x12, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0x81, 0x02, 0x0a, 0x13, + 0x4c, 0x69, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x3e, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x69, 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, 0xa9, 0x01, 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, 0x4a, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, + 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x69, + 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, + 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, 0x22, + 0x46, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, + 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x66, 0x65, 0x61, 0x73, + 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x08, 0x65, + 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, 0x9d, 0x02, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x3e, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x26, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 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, + 0xc5, 0x01, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x4a, 0x0a, 0x06, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x66, 0x65, 0x61, + 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x6c, 0x74, + 0x65, 0x72, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, + 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, + 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, + 0x65, 0x73, 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, 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, 0x22, 0xb8, 0x01, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x4a, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x1a, 0x54, 0x0a, 0x0d, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb8, 0x01, 0x0a, 0x14, 0x4c, 0x69, - 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, - 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x1a, 0x54, - 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 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, 0x2d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 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, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x3a, 0x02, 0x38, 0x01, 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, + 0x2d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 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, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 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, 0x5c, 0x0a, 0x12, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x45, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x70, 0x65, 0x63, 0x56, 0x32, + 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, + 0x22, 0x41, 0x0a, 0x13, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, + 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x06, 0x65, 0x6e, 0x74, + 0x69, 0x74, 0x79, 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, 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, + 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, 0x22, 0xb1, 0x02, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x65, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x53, 0x65, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 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, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x44, 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, + 0x18, 0x05, 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, 0x07, 0x65, 0x6e, 0x64, 0x44, 0x61, 0x74, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x69, + 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x06, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x0c, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, + 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, + 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x52, 0x65, + 0x66, 0x72, 0x65, 0x73, 0x68, 0x22, 0x9b, 0x01, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7b, 0x0a, 0x1f, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, + 0x74, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x73, + 0x74, 0x69, 0x63, 0x73, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 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, 0x44, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, + 0x73, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x1c, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x46, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x4c, + 0x69, 0x73, 0x74, 0x22, 0x94, 0x01, 0x0a, 0x1d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3d, 0x0a, 0x09, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, + 0x63, 0x65, 0x18, 0x01, 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, 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, + 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x66, 0x65, 0x72, + 0x65, 0x6e, 0x63, 0x65, 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, 0x22, 0x20, 0x0a, 0x1e, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xbe, 0x0a, 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, 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, 0x22, 0xb1, 0x02, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x65, - 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x65, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x73, 0x74, - 0x61, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 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, 0x73, 0x74, 0x61, 0x72, - 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x61, 0x74, - 0x65, 0x18, 0x05, 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, 0x07, 0x65, 0x6e, 0x64, 0x44, 0x61, 0x74, 0x65, 0x12, 0x23, 0x0a, 0x0d, - 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x06, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x0c, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, - 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x66, 0x72, 0x65, - 0x73, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x52, - 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x22, 0x9b, 0x01, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x46, 0x65, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7b, 0x0a, 0x1f, 0x64, 0x61, 0x74, 0x61, 0x73, - 0x65, 0x74, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x69, - 0x73, 0x74, 0x69, 0x63, 0x73, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 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, 0x44, 0x61, 0x74, 0x61, 0x73, 0x65, - 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, - 0x63, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x1c, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x46, - 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, - 0x4c, 0x69, 0x73, 0x74, 0x32, 0x89, 0x0a, 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, + 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, 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, 0x51, - 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x1f, - 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 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, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x69, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, - 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x12, 0x27, 0x2e, 0x66, 0x65, 0x61, 0x73, - 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, - 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, - 0x74, 0x69, 0x63, 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, + 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, 0x48, 0x0a, 0x09, 0x47, 0x65, + 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1c, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, + 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 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, 0x51, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, + 0x12, 0x1f, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 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, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x12, 0x27, 0x2e, 0x66, 0x65, + 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, + 0x65, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, + 0x69, 0x73, 0x74, 0x69, 0x63, 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, 0x41, 0x70, 0x70, 0x6c, 0x79, + 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1e, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x45, + 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x1f, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, + 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, + 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, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x69, + 0x65, 0x73, 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, 0x6f, 0x0a, 0x16, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x29, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x53, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x2a, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xbf, 0x02, + 0x0a, 0x14, 0x4a, 0x6f, 0x62, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 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, - 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, + 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, - 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, + 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 ( @@ -2100,7 +2650,7 @@ func file_feast_core_CoreService_proto_rawDescGZIP() []byte { } var file_feast_core_CoreService_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_feast_core_CoreService_proto_msgTypes = make([]protoimpl.MessageInfo, 35) +var file_feast_core_CoreService_proto_msgTypes = make([]protoimpl.MessageInfo, 45) var file_feast_core_CoreService_proto_goTypes = []interface{}{ (ApplyFeatureSetResponse_Status)(0), // 0: feast.core.ApplyFeatureSetResponse.Status (UpdateStoreResponse_Status)(0), // 1: feast.core.UpdateStoreResponse.Status @@ -2108,101 +2658,131 @@ var file_feast_core_CoreService_proto_goTypes = []interface{}{ (*GetFeatureSetResponse)(nil), // 3: feast.core.GetFeatureSetResponse (*ListFeatureSetsRequest)(nil), // 4: feast.core.ListFeatureSetsRequest (*ListFeatureSetsResponse)(nil), // 5: feast.core.ListFeatureSetsResponse - (*ListFeaturesRequest)(nil), // 6: feast.core.ListFeaturesRequest - (*ListFeaturesResponse)(nil), // 7: feast.core.ListFeaturesResponse - (*ListStoresRequest)(nil), // 8: feast.core.ListStoresRequest - (*ListStoresResponse)(nil), // 9: feast.core.ListStoresResponse - (*ApplyFeatureSetRequest)(nil), // 10: feast.core.ApplyFeatureSetRequest - (*ApplyFeatureSetResponse)(nil), // 11: feast.core.ApplyFeatureSetResponse - (*GetFeastCoreVersionRequest)(nil), // 12: feast.core.GetFeastCoreVersionRequest - (*GetFeastCoreVersionResponse)(nil), // 13: feast.core.GetFeastCoreVersionResponse - (*UpdateStoreRequest)(nil), // 14: feast.core.UpdateStoreRequest - (*UpdateStoreResponse)(nil), // 15: feast.core.UpdateStoreResponse - (*CreateProjectRequest)(nil), // 16: feast.core.CreateProjectRequest - (*CreateProjectResponse)(nil), // 17: feast.core.CreateProjectResponse - (*ArchiveProjectRequest)(nil), // 18: feast.core.ArchiveProjectRequest - (*ArchiveProjectResponse)(nil), // 19: feast.core.ArchiveProjectResponse - (*ListProjectsRequest)(nil), // 20: feast.core.ListProjectsRequest - (*ListProjectsResponse)(nil), // 21: feast.core.ListProjectsResponse - (*ListIngestionJobsRequest)(nil), // 22: feast.core.ListIngestionJobsRequest - (*ListIngestionJobsResponse)(nil), // 23: feast.core.ListIngestionJobsResponse - (*RestartIngestionJobRequest)(nil), // 24: feast.core.RestartIngestionJobRequest - (*RestartIngestionJobResponse)(nil), // 25: feast.core.RestartIngestionJobResponse - (*StopIngestionJobRequest)(nil), // 26: feast.core.StopIngestionJobRequest - (*StopIngestionJobResponse)(nil), // 27: feast.core.StopIngestionJobResponse - (*GetFeatureStatisticsRequest)(nil), // 28: feast.core.GetFeatureStatisticsRequest - (*GetFeatureStatisticsResponse)(nil), // 29: feast.core.GetFeatureStatisticsResponse - (*ListFeatureSetsRequest_Filter)(nil), // 30: feast.core.ListFeatureSetsRequest.Filter - nil, // 31: feast.core.ListFeatureSetsRequest.Filter.LabelsEntry - (*ListFeaturesRequest_Filter)(nil), // 32: feast.core.ListFeaturesRequest.Filter - nil, // 33: feast.core.ListFeaturesRequest.Filter.LabelsEntry - nil, // 34: feast.core.ListFeaturesResponse.FeaturesEntry - (*ListStoresRequest_Filter)(nil), // 35: feast.core.ListStoresRequest.Filter - (*ListIngestionJobsRequest_Filter)(nil), // 36: feast.core.ListIngestionJobsRequest.Filter - (*FeatureSet)(nil), // 37: feast.core.FeatureSet - (*Store)(nil), // 38: feast.core.Store - (*IngestionJob)(nil), // 39: feast.core.IngestionJob - (*timestamp.Timestamp)(nil), // 40: google.protobuf.Timestamp - (*v0.DatasetFeatureStatisticsList)(nil), // 41: tensorflow.metadata.v0.DatasetFeatureStatisticsList - (*FeatureSpec)(nil), // 42: feast.core.FeatureSpec - (*FeatureSetReference)(nil), // 43: feast.core.FeatureSetReference + (*GetEntityRequest)(nil), // 6: feast.core.GetEntityRequest + (*GetEntityResponse)(nil), // 7: feast.core.GetEntityResponse + (*ListEntitiesRequest)(nil), // 8: feast.core.ListEntitiesRequest + (*ListEntitiesResponse)(nil), // 9: feast.core.ListEntitiesResponse + (*ListFeaturesRequest)(nil), // 10: feast.core.ListFeaturesRequest + (*ListFeaturesResponse)(nil), // 11: feast.core.ListFeaturesResponse + (*ListStoresRequest)(nil), // 12: feast.core.ListStoresRequest + (*ListStoresResponse)(nil), // 13: feast.core.ListStoresResponse + (*ApplyEntityRequest)(nil), // 14: feast.core.ApplyEntityRequest + (*ApplyEntityResponse)(nil), // 15: feast.core.ApplyEntityResponse + (*ApplyFeatureSetRequest)(nil), // 16: feast.core.ApplyFeatureSetRequest + (*ApplyFeatureSetResponse)(nil), // 17: feast.core.ApplyFeatureSetResponse + (*GetFeastCoreVersionRequest)(nil), // 18: feast.core.GetFeastCoreVersionRequest + (*GetFeastCoreVersionResponse)(nil), // 19: feast.core.GetFeastCoreVersionResponse + (*UpdateStoreRequest)(nil), // 20: feast.core.UpdateStoreRequest + (*UpdateStoreResponse)(nil), // 21: feast.core.UpdateStoreResponse + (*CreateProjectRequest)(nil), // 22: feast.core.CreateProjectRequest + (*CreateProjectResponse)(nil), // 23: feast.core.CreateProjectResponse + (*ArchiveProjectRequest)(nil), // 24: feast.core.ArchiveProjectRequest + (*ArchiveProjectResponse)(nil), // 25: feast.core.ArchiveProjectResponse + (*ListProjectsRequest)(nil), // 26: feast.core.ListProjectsRequest + (*ListProjectsResponse)(nil), // 27: feast.core.ListProjectsResponse + (*ListIngestionJobsRequest)(nil), // 28: feast.core.ListIngestionJobsRequest + (*ListIngestionJobsResponse)(nil), // 29: feast.core.ListIngestionJobsResponse + (*RestartIngestionJobRequest)(nil), // 30: feast.core.RestartIngestionJobRequest + (*RestartIngestionJobResponse)(nil), // 31: feast.core.RestartIngestionJobResponse + (*StopIngestionJobRequest)(nil), // 32: feast.core.StopIngestionJobRequest + (*StopIngestionJobResponse)(nil), // 33: feast.core.StopIngestionJobResponse + (*GetFeatureStatisticsRequest)(nil), // 34: feast.core.GetFeatureStatisticsRequest + (*GetFeatureStatisticsResponse)(nil), // 35: feast.core.GetFeatureStatisticsResponse + (*UpdateFeatureSetStatusRequest)(nil), // 36: feast.core.UpdateFeatureSetStatusRequest + (*UpdateFeatureSetStatusResponse)(nil), // 37: feast.core.UpdateFeatureSetStatusResponse + (*ListFeatureSetsRequest_Filter)(nil), // 38: feast.core.ListFeatureSetsRequest.Filter + nil, // 39: feast.core.ListFeatureSetsRequest.Filter.LabelsEntry + (*ListEntitiesRequest_Filter)(nil), // 40: feast.core.ListEntitiesRequest.Filter + nil, // 41: feast.core.ListEntitiesRequest.Filter.LabelsEntry + (*ListFeaturesRequest_Filter)(nil), // 42: feast.core.ListFeaturesRequest.Filter + nil, // 43: feast.core.ListFeaturesRequest.Filter.LabelsEntry + nil, // 44: feast.core.ListFeaturesResponse.FeaturesEntry + (*ListStoresRequest_Filter)(nil), // 45: feast.core.ListStoresRequest.Filter + (*ListIngestionJobsRequest_Filter)(nil), // 46: feast.core.ListIngestionJobsRequest.Filter + (*FeatureSet)(nil), // 47: feast.core.FeatureSet + (*Entity)(nil), // 48: feast.core.Entity + (*Store)(nil), // 49: feast.core.Store + (*EntitySpecV2)(nil), // 50: feast.core.EntitySpecV2 + (*IngestionJob)(nil), // 51: feast.core.IngestionJob + (*timestamp.Timestamp)(nil), // 52: google.protobuf.Timestamp + (*v0.DatasetFeatureStatisticsList)(nil), // 53: tensorflow.metadata.v0.DatasetFeatureStatisticsList + (*FeatureSetReference)(nil), // 54: feast.core.FeatureSetReference + (FeatureSetStatus)(0), // 55: feast.core.FeatureSetStatus + (*FeatureSpec)(nil), // 56: feast.core.FeatureSpec } var file_feast_core_CoreService_proto_depIdxs = []int32{ - 37, // 0: feast.core.GetFeatureSetResponse.feature_set:type_name -> feast.core.FeatureSet - 30, // 1: feast.core.ListFeatureSetsRequest.filter:type_name -> feast.core.ListFeatureSetsRequest.Filter - 37, // 2: feast.core.ListFeatureSetsResponse.feature_sets:type_name -> feast.core.FeatureSet - 32, // 3: feast.core.ListFeaturesRequest.filter:type_name -> feast.core.ListFeaturesRequest.Filter - 34, // 4: feast.core.ListFeaturesResponse.features:type_name -> feast.core.ListFeaturesResponse.FeaturesEntry - 35, // 5: feast.core.ListStoresRequest.filter:type_name -> feast.core.ListStoresRequest.Filter - 38, // 6: feast.core.ListStoresResponse.store:type_name -> feast.core.Store - 37, // 7: feast.core.ApplyFeatureSetRequest.feature_set:type_name -> feast.core.FeatureSet - 37, // 8: feast.core.ApplyFeatureSetResponse.feature_set:type_name -> feast.core.FeatureSet - 0, // 9: feast.core.ApplyFeatureSetResponse.status:type_name -> feast.core.ApplyFeatureSetResponse.Status - 38, // 10: feast.core.UpdateStoreRequest.store:type_name -> feast.core.Store - 38, // 11: feast.core.UpdateStoreResponse.store:type_name -> feast.core.Store - 1, // 12: feast.core.UpdateStoreResponse.status:type_name -> feast.core.UpdateStoreResponse.Status - 36, // 13: feast.core.ListIngestionJobsRequest.filter:type_name -> feast.core.ListIngestionJobsRequest.Filter - 39, // 14: feast.core.ListIngestionJobsResponse.jobs:type_name -> feast.core.IngestionJob - 40, // 15: feast.core.GetFeatureStatisticsRequest.start_date:type_name -> google.protobuf.Timestamp - 40, // 16: feast.core.GetFeatureStatisticsRequest.end_date:type_name -> google.protobuf.Timestamp - 41, // 17: feast.core.GetFeatureStatisticsResponse.dataset_feature_statistics_list:type_name -> tensorflow.metadata.v0.DatasetFeatureStatisticsList - 31, // 18: feast.core.ListFeatureSetsRequest.Filter.labels:type_name -> feast.core.ListFeatureSetsRequest.Filter.LabelsEntry - 33, // 19: feast.core.ListFeaturesRequest.Filter.labels:type_name -> feast.core.ListFeaturesRequest.Filter.LabelsEntry - 42, // 20: feast.core.ListFeaturesResponse.FeaturesEntry.value:type_name -> feast.core.FeatureSpec - 43, // 21: feast.core.ListIngestionJobsRequest.Filter.feature_set_reference:type_name -> feast.core.FeatureSetReference - 12, // 22: feast.core.CoreService.GetFeastCoreVersion:input_type -> feast.core.GetFeastCoreVersionRequest - 2, // 23: feast.core.CoreService.GetFeatureSet:input_type -> feast.core.GetFeatureSetRequest - 4, // 24: feast.core.CoreService.ListFeatureSets:input_type -> feast.core.ListFeatureSetsRequest - 6, // 25: feast.core.CoreService.ListFeatures:input_type -> feast.core.ListFeaturesRequest - 28, // 26: feast.core.CoreService.GetFeatureStatistics:input_type -> feast.core.GetFeatureStatisticsRequest - 8, // 27: feast.core.CoreService.ListStores:input_type -> feast.core.ListStoresRequest - 10, // 28: feast.core.CoreService.ApplyFeatureSet:input_type -> feast.core.ApplyFeatureSetRequest - 14, // 29: feast.core.CoreService.UpdateStore:input_type -> feast.core.UpdateStoreRequest - 16, // 30: feast.core.CoreService.CreateProject:input_type -> feast.core.CreateProjectRequest - 18, // 31: feast.core.CoreService.ArchiveProject:input_type -> feast.core.ArchiveProjectRequest - 20, // 32: feast.core.CoreService.ListProjects:input_type -> feast.core.ListProjectsRequest - 22, // 33: feast.core.CoreService.ListIngestionJobs:input_type -> feast.core.ListIngestionJobsRequest - 24, // 34: feast.core.CoreService.RestartIngestionJob:input_type -> feast.core.RestartIngestionJobRequest - 26, // 35: feast.core.CoreService.StopIngestionJob:input_type -> feast.core.StopIngestionJobRequest - 13, // 36: feast.core.CoreService.GetFeastCoreVersion:output_type -> feast.core.GetFeastCoreVersionResponse - 3, // 37: feast.core.CoreService.GetFeatureSet:output_type -> feast.core.GetFeatureSetResponse - 5, // 38: feast.core.CoreService.ListFeatureSets:output_type -> feast.core.ListFeatureSetsResponse - 7, // 39: feast.core.CoreService.ListFeatures:output_type -> feast.core.ListFeaturesResponse - 29, // 40: feast.core.CoreService.GetFeatureStatistics:output_type -> feast.core.GetFeatureStatisticsResponse - 9, // 41: feast.core.CoreService.ListStores:output_type -> feast.core.ListStoresResponse - 11, // 42: feast.core.CoreService.ApplyFeatureSet:output_type -> feast.core.ApplyFeatureSetResponse - 15, // 43: feast.core.CoreService.UpdateStore:output_type -> feast.core.UpdateStoreResponse - 17, // 44: feast.core.CoreService.CreateProject:output_type -> feast.core.CreateProjectResponse - 19, // 45: feast.core.CoreService.ArchiveProject:output_type -> feast.core.ArchiveProjectResponse - 21, // 46: feast.core.CoreService.ListProjects:output_type -> feast.core.ListProjectsResponse - 23, // 47: feast.core.CoreService.ListIngestionJobs:output_type -> feast.core.ListIngestionJobsResponse - 25, // 48: feast.core.CoreService.RestartIngestionJob:output_type -> feast.core.RestartIngestionJobResponse - 27, // 49: feast.core.CoreService.StopIngestionJob:output_type -> feast.core.StopIngestionJobResponse - 36, // [36:50] is the sub-list for method output_type - 22, // [22:36] is the sub-list for method input_type - 22, // [22:22] is the sub-list for extension type_name - 22, // [22:22] is the sub-list for extension extendee - 0, // [0:22] is the sub-list for field type_name + 47, // 0: feast.core.GetFeatureSetResponse.feature_set:type_name -> feast.core.FeatureSet + 38, // 1: feast.core.ListFeatureSetsRequest.filter:type_name -> feast.core.ListFeatureSetsRequest.Filter + 47, // 2: feast.core.ListFeatureSetsResponse.feature_sets:type_name -> feast.core.FeatureSet + 48, // 3: feast.core.GetEntityResponse.entity:type_name -> feast.core.Entity + 40, // 4: feast.core.ListEntitiesRequest.filter:type_name -> feast.core.ListEntitiesRequest.Filter + 48, // 5: feast.core.ListEntitiesResponse.entities:type_name -> feast.core.Entity + 42, // 6: feast.core.ListFeaturesRequest.filter:type_name -> feast.core.ListFeaturesRequest.Filter + 44, // 7: feast.core.ListFeaturesResponse.features:type_name -> feast.core.ListFeaturesResponse.FeaturesEntry + 45, // 8: feast.core.ListStoresRequest.filter:type_name -> feast.core.ListStoresRequest.Filter + 49, // 9: feast.core.ListStoresResponse.store:type_name -> feast.core.Store + 50, // 10: feast.core.ApplyEntityRequest.spec:type_name -> feast.core.EntitySpecV2 + 48, // 11: feast.core.ApplyEntityResponse.entity:type_name -> feast.core.Entity + 47, // 12: feast.core.ApplyFeatureSetRequest.feature_set:type_name -> feast.core.FeatureSet + 47, // 13: feast.core.ApplyFeatureSetResponse.feature_set:type_name -> feast.core.FeatureSet + 0, // 14: feast.core.ApplyFeatureSetResponse.status:type_name -> feast.core.ApplyFeatureSetResponse.Status + 49, // 15: feast.core.UpdateStoreRequest.store:type_name -> feast.core.Store + 49, // 16: feast.core.UpdateStoreResponse.store:type_name -> feast.core.Store + 1, // 17: feast.core.UpdateStoreResponse.status:type_name -> feast.core.UpdateStoreResponse.Status + 46, // 18: feast.core.ListIngestionJobsRequest.filter:type_name -> feast.core.ListIngestionJobsRequest.Filter + 51, // 19: feast.core.ListIngestionJobsResponse.jobs:type_name -> feast.core.IngestionJob + 52, // 20: feast.core.GetFeatureStatisticsRequest.start_date:type_name -> google.protobuf.Timestamp + 52, // 21: feast.core.GetFeatureStatisticsRequest.end_date:type_name -> google.protobuf.Timestamp + 53, // 22: feast.core.GetFeatureStatisticsResponse.dataset_feature_statistics_list:type_name -> tensorflow.metadata.v0.DatasetFeatureStatisticsList + 54, // 23: feast.core.UpdateFeatureSetStatusRequest.reference:type_name -> feast.core.FeatureSetReference + 55, // 24: feast.core.UpdateFeatureSetStatusRequest.status:type_name -> feast.core.FeatureSetStatus + 39, // 25: feast.core.ListFeatureSetsRequest.Filter.labels:type_name -> feast.core.ListFeatureSetsRequest.Filter.LabelsEntry + 55, // 26: feast.core.ListFeatureSetsRequest.Filter.status:type_name -> feast.core.FeatureSetStatus + 41, // 27: feast.core.ListEntitiesRequest.Filter.labels:type_name -> feast.core.ListEntitiesRequest.Filter.LabelsEntry + 43, // 28: feast.core.ListFeaturesRequest.Filter.labels:type_name -> feast.core.ListFeaturesRequest.Filter.LabelsEntry + 56, // 29: feast.core.ListFeaturesResponse.FeaturesEntry.value:type_name -> feast.core.FeatureSpec + 54, // 30: feast.core.ListIngestionJobsRequest.Filter.feature_set_reference:type_name -> feast.core.FeatureSetReference + 18, // 31: feast.core.CoreService.GetFeastCoreVersion:input_type -> feast.core.GetFeastCoreVersionRequest + 2, // 32: feast.core.CoreService.GetFeatureSet:input_type -> feast.core.GetFeatureSetRequest + 6, // 33: feast.core.CoreService.GetEntity:input_type -> feast.core.GetEntityRequest + 4, // 34: feast.core.CoreService.ListFeatureSets:input_type -> feast.core.ListFeatureSetsRequest + 10, // 35: feast.core.CoreService.ListFeatures:input_type -> feast.core.ListFeaturesRequest + 34, // 36: feast.core.CoreService.GetFeatureStatistics:input_type -> feast.core.GetFeatureStatisticsRequest + 12, // 37: feast.core.CoreService.ListStores:input_type -> feast.core.ListStoresRequest + 16, // 38: feast.core.CoreService.ApplyFeatureSet:input_type -> feast.core.ApplyFeatureSetRequest + 14, // 39: feast.core.CoreService.ApplyEntity:input_type -> feast.core.ApplyEntityRequest + 8, // 40: feast.core.CoreService.ListEntities:input_type -> feast.core.ListEntitiesRequest + 20, // 41: feast.core.CoreService.UpdateStore:input_type -> feast.core.UpdateStoreRequest + 22, // 42: feast.core.CoreService.CreateProject:input_type -> feast.core.CreateProjectRequest + 24, // 43: feast.core.CoreService.ArchiveProject:input_type -> feast.core.ArchiveProjectRequest + 26, // 44: feast.core.CoreService.ListProjects:input_type -> feast.core.ListProjectsRequest + 36, // 45: feast.core.CoreService.UpdateFeatureSetStatus:input_type -> feast.core.UpdateFeatureSetStatusRequest + 28, // 46: feast.core.JobControllerService.ListIngestionJobs:input_type -> feast.core.ListIngestionJobsRequest + 30, // 47: feast.core.JobControllerService.RestartIngestionJob:input_type -> feast.core.RestartIngestionJobRequest + 32, // 48: feast.core.JobControllerService.StopIngestionJob:input_type -> feast.core.StopIngestionJobRequest + 19, // 49: feast.core.CoreService.GetFeastCoreVersion:output_type -> feast.core.GetFeastCoreVersionResponse + 3, // 50: feast.core.CoreService.GetFeatureSet:output_type -> feast.core.GetFeatureSetResponse + 7, // 51: feast.core.CoreService.GetEntity:output_type -> feast.core.GetEntityResponse + 5, // 52: feast.core.CoreService.ListFeatureSets:output_type -> feast.core.ListFeatureSetsResponse + 11, // 53: feast.core.CoreService.ListFeatures:output_type -> feast.core.ListFeaturesResponse + 35, // 54: feast.core.CoreService.GetFeatureStatistics:output_type -> feast.core.GetFeatureStatisticsResponse + 13, // 55: feast.core.CoreService.ListStores:output_type -> feast.core.ListStoresResponse + 17, // 56: feast.core.CoreService.ApplyFeatureSet:output_type -> feast.core.ApplyFeatureSetResponse + 15, // 57: feast.core.CoreService.ApplyEntity:output_type -> feast.core.ApplyEntityResponse + 9, // 58: feast.core.CoreService.ListEntities:output_type -> feast.core.ListEntitiesResponse + 21, // 59: feast.core.CoreService.UpdateStore:output_type -> feast.core.UpdateStoreResponse + 23, // 60: feast.core.CoreService.CreateProject:output_type -> feast.core.CreateProjectResponse + 25, // 61: feast.core.CoreService.ArchiveProject:output_type -> feast.core.ArchiveProjectResponse + 27, // 62: feast.core.CoreService.ListProjects:output_type -> feast.core.ListProjectsResponse + 37, // 63: feast.core.CoreService.UpdateFeatureSetStatus:output_type -> feast.core.UpdateFeatureSetStatusResponse + 29, // 64: feast.core.JobControllerService.ListIngestionJobs:output_type -> feast.core.ListIngestionJobsResponse + 31, // 65: feast.core.JobControllerService.RestartIngestionJob:output_type -> feast.core.RestartIngestionJobResponse + 33, // 66: feast.core.JobControllerService.StopIngestionJob:output_type -> feast.core.StopIngestionJobResponse + 49, // [49:67] is the sub-list for method output_type + 31, // [31:49] is the sub-list for method input_type + 31, // [31:31] is the sub-list for extension type_name + 31, // [31:31] is the sub-list for extension extendee + 0, // [0:31] is the sub-list for field type_name } func init() { file_feast_core_CoreService_proto_init() } @@ -2210,6 +2790,7 @@ func file_feast_core_CoreService_proto_init() { if File_feast_core_CoreService_proto != nil { return } + file_feast_core_Entity_proto_init() file_feast_core_FeatureSet_proto_init() file_feast_core_Store_proto_init() file_feast_core_FeatureSetReference_proto_init() @@ -2264,7 +2845,7 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListFeaturesRequest); i { + switch v := v.(*GetEntityRequest); i { case 0: return &v.state case 1: @@ -2276,7 +2857,7 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListFeaturesResponse); i { + switch v := v.(*GetEntityResponse); i { case 0: return &v.state case 1: @@ -2288,7 +2869,7 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListStoresRequest); i { + switch v := v.(*ListEntitiesRequest); i { case 0: return &v.state case 1: @@ -2300,7 +2881,7 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListStoresResponse); i { + switch v := v.(*ListEntitiesResponse); i { case 0: return &v.state case 1: @@ -2312,7 +2893,7 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ApplyFeatureSetRequest); i { + switch v := v.(*ListFeaturesRequest); i { case 0: return &v.state case 1: @@ -2324,7 +2905,7 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ApplyFeatureSetResponse); i { + switch v := v.(*ListFeaturesResponse); i { case 0: return &v.state case 1: @@ -2336,7 +2917,7 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetFeastCoreVersionRequest); i { + switch v := v.(*ListStoresRequest); i { case 0: return &v.state case 1: @@ -2348,7 +2929,7 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetFeastCoreVersionResponse); i { + switch v := v.(*ListStoresResponse); i { case 0: return &v.state case 1: @@ -2360,7 +2941,7 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateStoreRequest); i { + switch v := v.(*ApplyEntityRequest); i { case 0: return &v.state case 1: @@ -2372,7 +2953,7 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateStoreResponse); i { + switch v := v.(*ApplyEntityResponse); i { case 0: return &v.state case 1: @@ -2384,7 +2965,7 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CreateProjectRequest); i { + switch v := v.(*ApplyFeatureSetRequest); i { case 0: return &v.state case 1: @@ -2396,7 +2977,7 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CreateProjectResponse); i { + switch v := v.(*ApplyFeatureSetResponse); i { case 0: return &v.state case 1: @@ -2408,7 +2989,7 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ArchiveProjectRequest); i { + switch v := v.(*GetFeastCoreVersionRequest); i { case 0: return &v.state case 1: @@ -2420,7 +3001,7 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ArchiveProjectResponse); i { + switch v := v.(*GetFeastCoreVersionResponse); i { case 0: return &v.state case 1: @@ -2432,7 +3013,7 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListProjectsRequest); i { + switch v := v.(*UpdateStoreRequest); i { case 0: return &v.state case 1: @@ -2444,7 +3025,7 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListProjectsResponse); i { + switch v := v.(*UpdateStoreResponse); i { case 0: return &v.state case 1: @@ -2456,7 +3037,7 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListIngestionJobsRequest); i { + switch v := v.(*CreateProjectRequest); i { case 0: return &v.state case 1: @@ -2468,7 +3049,7 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListIngestionJobsResponse); i { + switch v := v.(*CreateProjectResponse); i { case 0: return &v.state case 1: @@ -2480,7 +3061,7 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RestartIngestionJobRequest); i { + switch v := v.(*ArchiveProjectRequest); i { case 0: return &v.state case 1: @@ -2492,7 +3073,7 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RestartIngestionJobResponse); i { + switch v := v.(*ArchiveProjectResponse); i { case 0: return &v.state case 1: @@ -2504,7 +3085,7 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StopIngestionJobRequest); i { + switch v := v.(*ListProjectsRequest); i { case 0: return &v.state case 1: @@ -2516,7 +3097,7 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StopIngestionJobResponse); i { + switch v := v.(*ListProjectsResponse); i { case 0: return &v.state case 1: @@ -2528,7 +3109,7 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetFeatureStatisticsRequest); i { + switch v := v.(*ListIngestionJobsRequest); i { case 0: return &v.state case 1: @@ -2540,7 +3121,7 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetFeatureStatisticsResponse); i { + switch v := v.(*ListIngestionJobsResponse); i { case 0: return &v.state case 1: @@ -2552,7 +3133,19 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListFeatureSetsRequest_Filter); i { + 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[29].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RestartIngestionJobResponse); i { case 0: return &v.state case 1: @@ -2564,7 +3157,31 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListFeaturesRequest_Filter); i { + 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[31].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[32].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetFeatureStatisticsRequest); i { case 0: return &v.state case 1: @@ -2576,7 +3193,7 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListStoresRequest_Filter); i { + switch v := v.(*GetFeatureStatisticsResponse); i { case 0: return &v.state case 1: @@ -2588,6 +3205,78 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateFeatureSetStatusRequest); 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[35].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateFeatureSetStatusResponse); 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[36].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[38].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListEntitiesRequest_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[40].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListFeaturesRequest_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[43].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[44].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ListIngestionJobsRequest_Filter); i { case 0: return &v.state @@ -2606,9 +3295,9 @@ func file_feast_core_CoreService_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_feast_core_CoreService_proto_rawDesc, NumEnums: 2, - NumMessages: 35, + NumMessages: 45, NumExtensions: 0, - NumServices: 1, + NumServices: 2, }, GoTypes: file_feast_core_CoreService_proto_goTypes, DependencyIndexes: file_feast_core_CoreService_proto_depIdxs, @@ -2637,6 +3326,8 @@ type CoreServiceClient interface { GetFeastCoreVersion(ctx context.Context, in *GetFeastCoreVersionRequest, opts ...grpc.CallOption) (*GetFeastCoreVersionResponse, error) // Returns a specific feature set GetFeatureSet(ctx context.Context, in *GetFeatureSetRequest, opts ...grpc.CallOption) (*GetFeatureSetResponse, error) + // Returns a specific entity + GetEntity(ctx context.Context, in *GetEntityRequest, opts ...grpc.CallOption) (*GetEntityResponse, error) // Retrieve feature set details given a filter. // // Returns all feature sets matching that filter. If none are found, @@ -2668,6 +3359,19 @@ type CoreServiceClient interface { // - Changes to entities // - Changes to feature name and type ApplyFeatureSet(ctx context.Context, in *ApplyFeatureSetRequest, opts ...grpc.CallOption) (*ApplyFeatureSetResponse, error) + // Create or update and existing entity. + // + // This function is idempotent - it will not create a new entity if schema does not change. + // Schema changes will update the entity if the changes are valid. + // Following changes are not valid: + // - Changes to name + // - Changes to type + ApplyEntity(ctx context.Context, in *ApplyEntityRequest, opts ...grpc.CallOption) (*ApplyEntityResponse, error) + // Returns all entity references and respective entities matching that filter. If none are found + // an empty map will be returned + // If no filter is provided in the request, the response will contain all the entities + // currently stored in the default project. + ListEntities(ctx context.Context, in *ListEntitiesRequest, opts ...grpc.CallOption) (*ListEntitiesResponse, error) // Updates core with the configuration of the store. // // If the changes are valid, core will return the given store configuration in response, and @@ -2684,20 +3388,8 @@ 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) + // Internal API for Job Controller to update featureSet's status once responsible ingestion job is running + UpdateFeatureSetStatus(ctx context.Context, in *UpdateFeatureSetStatusRequest, opts ...grpc.CallOption) (*UpdateFeatureSetStatusResponse, error) } type coreServiceClient struct { @@ -2726,6 +3418,15 @@ func (c *coreServiceClient) GetFeatureSet(ctx context.Context, in *GetFeatureSet return out, nil } +func (c *coreServiceClient) GetEntity(ctx context.Context, in *GetEntityRequest, opts ...grpc.CallOption) (*GetEntityResponse, error) { + out := new(GetEntityResponse) + err := c.cc.Invoke(ctx, "/feast.core.CoreService/GetEntity", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *coreServiceClient) ListFeatureSets(ctx context.Context, in *ListFeatureSetsRequest, opts ...grpc.CallOption) (*ListFeatureSetsResponse, error) { out := new(ListFeatureSetsResponse) err := c.cc.Invoke(ctx, "/feast.core.CoreService/ListFeatureSets", in, out, opts...) @@ -2771,63 +3472,63 @@ func (c *coreServiceClient) ApplyFeatureSet(ctx context.Context, in *ApplyFeatur return out, nil } -func (c *coreServiceClient) UpdateStore(ctx context.Context, in *UpdateStoreRequest, opts ...grpc.CallOption) (*UpdateStoreResponse, error) { - out := new(UpdateStoreResponse) - err := c.cc.Invoke(ctx, "/feast.core.CoreService/UpdateStore", in, out, opts...) +func (c *coreServiceClient) ApplyEntity(ctx context.Context, in *ApplyEntityRequest, opts ...grpc.CallOption) (*ApplyEntityResponse, error) { + out := new(ApplyEntityResponse) + err := c.cc.Invoke(ctx, "/feast.core.CoreService/ApplyEntity", in, out, opts...) if err != nil { return nil, err } return out, nil } -func (c *coreServiceClient) CreateProject(ctx context.Context, in *CreateProjectRequest, opts ...grpc.CallOption) (*CreateProjectResponse, error) { - out := new(CreateProjectResponse) - err := c.cc.Invoke(ctx, "/feast.core.CoreService/CreateProject", in, out, opts...) +func (c *coreServiceClient) ListEntities(ctx context.Context, in *ListEntitiesRequest, opts ...grpc.CallOption) (*ListEntitiesResponse, error) { + out := new(ListEntitiesResponse) + err := c.cc.Invoke(ctx, "/feast.core.CoreService/ListEntities", in, out, opts...) if err != nil { return nil, err } return out, nil } -func (c *coreServiceClient) ArchiveProject(ctx context.Context, in *ArchiveProjectRequest, opts ...grpc.CallOption) (*ArchiveProjectResponse, error) { - out := new(ArchiveProjectResponse) - err := c.cc.Invoke(ctx, "/feast.core.CoreService/ArchiveProject", in, out, opts...) +func (c *coreServiceClient) UpdateStore(ctx context.Context, in *UpdateStoreRequest, opts ...grpc.CallOption) (*UpdateStoreResponse, error) { + out := new(UpdateStoreResponse) + err := c.cc.Invoke(ctx, "/feast.core.CoreService/UpdateStore", in, out, opts...) if err != nil { return nil, err } return out, nil } -func (c *coreServiceClient) ListProjects(ctx context.Context, in *ListProjectsRequest, opts ...grpc.CallOption) (*ListProjectsResponse, error) { - out := new(ListProjectsResponse) - err := c.cc.Invoke(ctx, "/feast.core.CoreService/ListProjects", in, out, opts...) +func (c *coreServiceClient) CreateProject(ctx context.Context, in *CreateProjectRequest, opts ...grpc.CallOption) (*CreateProjectResponse, error) { + out := new(CreateProjectResponse) + err := c.cc.Invoke(ctx, "/feast.core.CoreService/CreateProject", in, out, opts...) if err != nil { return nil, err } 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...) +func (c *coreServiceClient) ArchiveProject(ctx context.Context, in *ArchiveProjectRequest, opts ...grpc.CallOption) (*ArchiveProjectResponse, error) { + out := new(ArchiveProjectResponse) + err := c.cc.Invoke(ctx, "/feast.core.CoreService/ArchiveProject", 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...) +func (c *coreServiceClient) ListProjects(ctx context.Context, in *ListProjectsRequest, opts ...grpc.CallOption) (*ListProjectsResponse, error) { + out := new(ListProjectsResponse) + err := c.cc.Invoke(ctx, "/feast.core.CoreService/ListProjects", 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...) +func (c *coreServiceClient) UpdateFeatureSetStatus(ctx context.Context, in *UpdateFeatureSetStatusRequest, opts ...grpc.CallOption) (*UpdateFeatureSetStatusResponse, error) { + out := new(UpdateFeatureSetStatusResponse) + err := c.cc.Invoke(ctx, "/feast.core.CoreService/UpdateFeatureSetStatus", in, out, opts...) if err != nil { return nil, err } @@ -2840,6 +3541,8 @@ type CoreServiceServer interface { GetFeastCoreVersion(context.Context, *GetFeastCoreVersionRequest) (*GetFeastCoreVersionResponse, error) // Returns a specific feature set GetFeatureSet(context.Context, *GetFeatureSetRequest) (*GetFeatureSetResponse, error) + // Returns a specific entity + GetEntity(context.Context, *GetEntityRequest) (*GetEntityResponse, error) // Retrieve feature set details given a filter. // // Returns all feature sets matching that filter. If none are found, @@ -2871,6 +3574,19 @@ type CoreServiceServer interface { // - Changes to entities // - Changes to feature name and type ApplyFeatureSet(context.Context, *ApplyFeatureSetRequest) (*ApplyFeatureSetResponse, error) + // Create or update and existing entity. + // + // This function is idempotent - it will not create a new entity if schema does not change. + // Schema changes will update the entity if the changes are valid. + // Following changes are not valid: + // - Changes to name + // - Changes to type + ApplyEntity(context.Context, *ApplyEntityRequest) (*ApplyEntityResponse, error) + // Returns all entity references and respective entities matching that filter. If none are found + // an empty map will be returned + // If no filter is provided in the request, the response will contain all the entities + // currently stored in the default project. + ListEntities(context.Context, *ListEntitiesRequest) (*ListEntitiesResponse, error) // Updates core with the configuration of the store. // // If the changes are valid, core will return the given store configuration in response, and @@ -2887,20 +3603,8 @@ 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) + // Internal API for Job Controller to update featureSet's status once responsible ingestion job is running + UpdateFeatureSetStatus(context.Context, *UpdateFeatureSetStatusRequest) (*UpdateFeatureSetStatusResponse, error) } // UnimplementedCoreServiceServer can be embedded to have forward compatible implementations. @@ -2913,6 +3617,9 @@ func (*UnimplementedCoreServiceServer) GetFeastCoreVersion(context.Context, *Get func (*UnimplementedCoreServiceServer) GetFeatureSet(context.Context, *GetFeatureSetRequest) (*GetFeatureSetResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetFeatureSet not implemented") } +func (*UnimplementedCoreServiceServer) GetEntity(context.Context, *GetEntityRequest) (*GetEntityResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetEntity not implemented") +} func (*UnimplementedCoreServiceServer) ListFeatureSets(context.Context, *ListFeatureSetsRequest) (*ListFeatureSetsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListFeatureSets not implemented") } @@ -2928,6 +3635,12 @@ func (*UnimplementedCoreServiceServer) ListStores(context.Context, *ListStoresRe func (*UnimplementedCoreServiceServer) ApplyFeatureSet(context.Context, *ApplyFeatureSetRequest) (*ApplyFeatureSetResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ApplyFeatureSet not implemented") } +func (*UnimplementedCoreServiceServer) ApplyEntity(context.Context, *ApplyEntityRequest) (*ApplyEntityResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ApplyEntity not implemented") +} +func (*UnimplementedCoreServiceServer) ListEntities(context.Context, *ListEntitiesRequest) (*ListEntitiesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListEntities not implemented") +} func (*UnimplementedCoreServiceServer) UpdateStore(context.Context, *UpdateStoreRequest) (*UpdateStoreResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateStore not implemented") } @@ -2940,14 +3653,8 @@ func (*UnimplementedCoreServiceServer) ArchiveProject(context.Context, *ArchiveP 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 (*UnimplementedCoreServiceServer) UpdateFeatureSetStatus(context.Context, *UpdateFeatureSetStatusRequest) (*UpdateFeatureSetStatusResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateFeatureSetStatus not implemented") } func RegisterCoreServiceServer(s *grpc.Server, srv CoreServiceServer) { @@ -2990,6 +3697,24 @@ func _CoreService_GetFeatureSet_Handler(srv interface{}, ctx context.Context, de return interceptor(ctx, in, info, handler) } +func _CoreService_GetEntity_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetEntityRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CoreServiceServer).GetEntity(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/feast.core.CoreService/GetEntity", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CoreServiceServer).GetEntity(ctx, req.(*GetEntityRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _CoreService_ListFeatureSets_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListFeatureSetsRequest) if err := dec(in); err != nil { @@ -3080,128 +3805,128 @@ func _CoreService_ApplyFeatureSet_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } -func _CoreService_UpdateStore_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(UpdateStoreRequest) +func _CoreService_ApplyEntity_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ApplyEntityRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(CoreServiceServer).UpdateStore(ctx, in) + return srv.(CoreServiceServer).ApplyEntity(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/feast.core.CoreService/UpdateStore", + FullMethod: "/feast.core.CoreService/ApplyEntity", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServiceServer).UpdateStore(ctx, req.(*UpdateStoreRequest)) + return srv.(CoreServiceServer).ApplyEntity(ctx, req.(*ApplyEntityRequest)) } return interceptor(ctx, in, info, handler) } -func _CoreService_CreateProject_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(CreateProjectRequest) +func _CoreService_ListEntities_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListEntitiesRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(CoreServiceServer).CreateProject(ctx, in) + return srv.(CoreServiceServer).ListEntities(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/feast.core.CoreService/CreateProject", + FullMethod: "/feast.core.CoreService/ListEntities", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServiceServer).CreateProject(ctx, req.(*CreateProjectRequest)) + return srv.(CoreServiceServer).ListEntities(ctx, req.(*ListEntitiesRequest)) } return interceptor(ctx, in, info, handler) } -func _CoreService_ArchiveProject_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ArchiveProjectRequest) +func _CoreService_UpdateStore_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateStoreRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(CoreServiceServer).ArchiveProject(ctx, in) + return srv.(CoreServiceServer).UpdateStore(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/feast.core.CoreService/ArchiveProject", + FullMethod: "/feast.core.CoreService/UpdateStore", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServiceServer).ArchiveProject(ctx, req.(*ArchiveProjectRequest)) + return srv.(CoreServiceServer).UpdateStore(ctx, req.(*UpdateStoreRequest)) } return interceptor(ctx, in, info, handler) } -func _CoreService_ListProjects_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ListProjectsRequest) +func _CoreService_CreateProject_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateProjectRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(CoreServiceServer).ListProjects(ctx, in) + return srv.(CoreServiceServer).CreateProject(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/feast.core.CoreService/ListProjects", + FullMethod: "/feast.core.CoreService/CreateProject", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServiceServer).ListProjects(ctx, req.(*ListProjectsRequest)) + return srv.(CoreServiceServer).CreateProject(ctx, req.(*CreateProjectRequest)) } 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) +func _CoreService_ArchiveProject_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ArchiveProjectRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(CoreServiceServer).ListIngestionJobs(ctx, in) + return srv.(CoreServiceServer).ArchiveProject(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/feast.core.CoreService/ListIngestionJobs", + FullMethod: "/feast.core.CoreService/ArchiveProject", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServiceServer).ListIngestionJobs(ctx, req.(*ListIngestionJobsRequest)) + return srv.(CoreServiceServer).ArchiveProject(ctx, req.(*ArchiveProjectRequest)) } 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) +func _CoreService_ListProjects_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListProjectsRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(CoreServiceServer).RestartIngestionJob(ctx, in) + return srv.(CoreServiceServer).ListProjects(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/feast.core.CoreService/RestartIngestionJob", + FullMethod: "/feast.core.CoreService/ListProjects", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServiceServer).RestartIngestionJob(ctx, req.(*RestartIngestionJobRequest)) + return srv.(CoreServiceServer).ListProjects(ctx, req.(*ListProjectsRequest)) } 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) +func _CoreService_UpdateFeatureSetStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateFeatureSetStatusRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(CoreServiceServer).StopIngestionJob(ctx, in) + return srv.(CoreServiceServer).UpdateFeatureSetStatus(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/feast.core.CoreService/StopIngestionJob", + FullMethod: "/feast.core.CoreService/UpdateFeatureSetStatus", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServiceServer).StopIngestionJob(ctx, req.(*StopIngestionJobRequest)) + return srv.(CoreServiceServer).UpdateFeatureSetStatus(ctx, req.(*UpdateFeatureSetStatusRequest)) } return interceptor(ctx, in, info, handler) } @@ -3218,6 +3943,10 @@ var _CoreService_serviceDesc = grpc.ServiceDesc{ MethodName: "GetFeatureSet", Handler: _CoreService_GetFeatureSet_Handler, }, + { + MethodName: "GetEntity", + Handler: _CoreService_GetEntity_Handler, + }, { MethodName: "ListFeatureSets", Handler: _CoreService_ListFeatureSets_Handler, @@ -3238,6 +3967,14 @@ var _CoreService_serviceDesc = grpc.ServiceDesc{ MethodName: "ApplyFeatureSet", Handler: _CoreService_ApplyFeatureSet_Handler, }, + { + MethodName: "ApplyEntity", + Handler: _CoreService_ApplyEntity_Handler, + }, + { + MethodName: "ListEntities", + Handler: _CoreService_ListEntities_Handler, + }, { MethodName: "UpdateStore", Handler: _CoreService_UpdateStore_Handler, @@ -3254,17 +3991,175 @@ var _CoreService_serviceDesc = grpc.ServiceDesc{ MethodName: "ListProjects", Handler: _CoreService_ListProjects_Handler, }, + { + MethodName: "UpdateFeatureSetStatus", + Handler: _CoreService_UpdateFeatureSetStatus_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "feast/core/CoreService.proto", +} + +// JobControllerServiceClient is the client API for JobControllerService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type JobControllerServiceClient interface { + // 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 jobControllerServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewJobControllerServiceClient(cc grpc.ClientConnInterface) JobControllerServiceClient { + return &jobControllerServiceClient{cc} +} + +func (c *jobControllerServiceClient) ListIngestionJobs(ctx context.Context, in *ListIngestionJobsRequest, opts ...grpc.CallOption) (*ListIngestionJobsResponse, error) { + out := new(ListIngestionJobsResponse) + err := c.cc.Invoke(ctx, "/feast.core.JobControllerService/ListIngestionJobs", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *jobControllerServiceClient) RestartIngestionJob(ctx context.Context, in *RestartIngestionJobRequest, opts ...grpc.CallOption) (*RestartIngestionJobResponse, error) { + out := new(RestartIngestionJobResponse) + err := c.cc.Invoke(ctx, "/feast.core.JobControllerService/RestartIngestionJob", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *jobControllerServiceClient) StopIngestionJob(ctx context.Context, in *StopIngestionJobRequest, opts ...grpc.CallOption) (*StopIngestionJobResponse, error) { + out := new(StopIngestionJobResponse) + err := c.cc.Invoke(ctx, "/feast.core.JobControllerService/StopIngestionJob", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// JobControllerServiceServer is the server API for JobControllerService service. +type JobControllerServiceServer interface { + // 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) +} + +// UnimplementedJobControllerServiceServer can be embedded to have forward compatible implementations. +type UnimplementedJobControllerServiceServer struct { +} + +func (*UnimplementedJobControllerServiceServer) ListIngestionJobs(context.Context, *ListIngestionJobsRequest) (*ListIngestionJobsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListIngestionJobs not implemented") +} +func (*UnimplementedJobControllerServiceServer) RestartIngestionJob(context.Context, *RestartIngestionJobRequest) (*RestartIngestionJobResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RestartIngestionJob not implemented") +} +func (*UnimplementedJobControllerServiceServer) StopIngestionJob(context.Context, *StopIngestionJobRequest) (*StopIngestionJobResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method StopIngestionJob not implemented") +} + +func RegisterJobControllerServiceServer(s *grpc.Server, srv JobControllerServiceServer) { + s.RegisterService(&_JobControllerService_serviceDesc, srv) +} + +func _JobControllerService_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.(JobControllerServiceServer).ListIngestionJobs(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/feast.core.JobControllerService/ListIngestionJobs", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(JobControllerServiceServer).ListIngestionJobs(ctx, req.(*ListIngestionJobsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _JobControllerService_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.(JobControllerServiceServer).RestartIngestionJob(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/feast.core.JobControllerService/RestartIngestionJob", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(JobControllerServiceServer).RestartIngestionJob(ctx, req.(*RestartIngestionJobRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _JobControllerService_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.(JobControllerServiceServer).StopIngestionJob(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/feast.core.JobControllerService/StopIngestionJob", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(JobControllerServiceServer).StopIngestionJob(ctx, req.(*StopIngestionJobRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _JobControllerService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "feast.core.JobControllerService", + HandlerType: (*JobControllerServiceServer)(nil), + Methods: []grpc.MethodDesc{ { MethodName: "ListIngestionJobs", - Handler: _CoreService_ListIngestionJobs_Handler, + Handler: _JobControllerService_ListIngestionJobs_Handler, }, { MethodName: "RestartIngestionJob", - Handler: _CoreService_RestartIngestionJob_Handler, + Handler: _JobControllerService_RestartIngestionJob_Handler, }, { MethodName: "StopIngestionJob", - Handler: _CoreService_StopIngestionJob_Handler, + Handler: _JobControllerService_StopIngestionJob_Handler, }, }, Streams: []grpc.StreamDesc{}, diff --git a/sdk/go/protos/feast/core/Entity.pb.go b/sdk/go/protos/feast/core/Entity.pb.go new file mode 100644 index 00000000000..0aed9133257 --- /dev/null +++ b/sdk/go/protos/feast/core/Entity.pb.go @@ -0,0 +1,379 @@ +// +// * 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.25.0 +// protoc v3.12.4 +// source: feast/core/Entity.proto + +package core + +import ( + types "github.com/feast-dev/feast/sdk/go/protos/feast/types" + proto "github.com/golang/protobuf/proto" + timestamp "github.com/golang/protobuf/ptypes/timestamp" + 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 Entity struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // User-specified specifications of this entity. + Spec *EntitySpecV2 `protobuf:"bytes,1,opt,name=spec,proto3" json:"spec,omitempty"` + // System-populated metadata for this entity. + Meta *EntityMeta `protobuf:"bytes,2,opt,name=meta,proto3" json:"meta,omitempty"` +} + +func (x *Entity) Reset() { + *x = Entity{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_Entity_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Entity) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Entity) ProtoMessage() {} + +func (x *Entity) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_Entity_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 Entity.ProtoReflect.Descriptor instead. +func (*Entity) Descriptor() ([]byte, []int) { + return file_feast_core_Entity_proto_rawDescGZIP(), []int{0} +} + +func (x *Entity) GetSpec() *EntitySpecV2 { + if x != nil { + return x.Spec + } + return nil +} + +func (x *Entity) GetMeta() *EntityMeta { + if x != nil { + return x.Meta + } + return nil +} + +type EntitySpecV2 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"` + // 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"` + // Description of the entity. + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,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 (x *EntitySpecV2) Reset() { + *x = EntitySpecV2{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_Entity_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EntitySpecV2) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EntitySpecV2) ProtoMessage() {} + +func (x *EntitySpecV2) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_Entity_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 EntitySpecV2.ProtoReflect.Descriptor instead. +func (*EntitySpecV2) Descriptor() ([]byte, []int) { + return file_feast_core_Entity_proto_rawDescGZIP(), []int{1} +} + +func (x *EntitySpecV2) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *EntitySpecV2) GetValueType() types.ValueType_Enum { + if x != nil { + return x.ValueType + } + return types.ValueType_INVALID +} + +func (x *EntitySpecV2) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *EntitySpecV2) GetLabels() map[string]string { + if x != nil { + return x.Labels + } + return nil +} + +type EntityMeta struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CreatedTimestamp *timestamp.Timestamp `protobuf:"bytes,1,opt,name=created_timestamp,json=createdTimestamp,proto3" json:"created_timestamp,omitempty"` + LastUpdatedTimestamp *timestamp.Timestamp `protobuf:"bytes,2,opt,name=last_updated_timestamp,json=lastUpdatedTimestamp,proto3" json:"last_updated_timestamp,omitempty"` +} + +func (x *EntityMeta) Reset() { + *x = EntityMeta{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_Entity_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EntityMeta) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EntityMeta) ProtoMessage() {} + +func (x *EntityMeta) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_Entity_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 EntityMeta.ProtoReflect.Descriptor instead. +func (*EntityMeta) Descriptor() ([]byte, []int) { + return file_feast_core_Entity_proto_rawDescGZIP(), []int{2} +} + +func (x *EntityMeta) GetCreatedTimestamp() *timestamp.Timestamp { + if x != nil { + return x.CreatedTimestamp + } + return nil +} + +func (x *EntityMeta) GetLastUpdatedTimestamp() *timestamp.Timestamp { + if x != nil { + return x.LastUpdatedTimestamp + } + return nil +} + +var File_feast_core_Entity_proto protoreflect.FileDescriptor + +var file_feast_core_Entity_proto_rawDesc = []byte{ + 0x0a, 0x17, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x45, 0x6e, 0x74, + 0x69, 0x74, 0x79, 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, 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, 0x22, + 0x62, 0x0a, 0x06, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x2c, 0x0a, 0x04, 0x73, 0x70, 0x65, + 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, + 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x70, 0x65, 0x63, 0x56, + 0x32, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x12, 0x2a, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, + 0x65, 0x74, 0x61, 0x22, 0xf9, 0x01, 0x0a, 0x0c, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x70, + 0x65, 0x63, 0x56, 0x32, 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, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3c, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, + 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x70, 0x65, 0x63, 0x56, 0x32, + 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, 0x22, + 0xa7, 0x01, 0x0a, 0x0a, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 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, 0x50, 0x0a, 0x16, 0x6c, 0x61, 0x73, 0x74, 0x5f, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x18, 0x02, 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, 0x14, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, + 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x54, 0x0a, 0x10, 0x66, 0x65, 0x61, + 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x42, 0x0b, 0x45, + 0x6e, 0x74, 0x69, 0x74, 0x79, 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_Entity_proto_rawDescOnce sync.Once + file_feast_core_Entity_proto_rawDescData = file_feast_core_Entity_proto_rawDesc +) + +func file_feast_core_Entity_proto_rawDescGZIP() []byte { + file_feast_core_Entity_proto_rawDescOnce.Do(func() { + file_feast_core_Entity_proto_rawDescData = protoimpl.X.CompressGZIP(file_feast_core_Entity_proto_rawDescData) + }) + return file_feast_core_Entity_proto_rawDescData +} + +var file_feast_core_Entity_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_feast_core_Entity_proto_goTypes = []interface{}{ + (*Entity)(nil), // 0: feast.core.Entity + (*EntitySpecV2)(nil), // 1: feast.core.EntitySpecV2 + (*EntityMeta)(nil), // 2: feast.core.EntityMeta + nil, // 3: feast.core.EntitySpecV2.LabelsEntry + (types.ValueType_Enum)(0), // 4: feast.types.ValueType.Enum + (*timestamp.Timestamp)(nil), // 5: google.protobuf.Timestamp +} +var file_feast_core_Entity_proto_depIdxs = []int32{ + 1, // 0: feast.core.Entity.spec:type_name -> feast.core.EntitySpecV2 + 2, // 1: feast.core.Entity.meta:type_name -> feast.core.EntityMeta + 4, // 2: feast.core.EntitySpecV2.value_type:type_name -> feast.types.ValueType.Enum + 3, // 3: feast.core.EntitySpecV2.labels:type_name -> feast.core.EntitySpecV2.LabelsEntry + 5, // 4: feast.core.EntityMeta.created_timestamp:type_name -> google.protobuf.Timestamp + 5, // 5: feast.core.EntityMeta.last_updated_timestamp:type_name -> google.protobuf.Timestamp + 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_Entity_proto_init() } +func file_feast_core_Entity_proto_init() { + if File_feast_core_Entity_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_feast_core_Entity_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Entity); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_Entity_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*EntitySpecV2); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_Entity_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*EntityMeta); 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_Entity_proto_rawDesc, + NumEnums: 0, + NumMessages: 4, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_feast_core_Entity_proto_goTypes, + DependencyIndexes: file_feast_core_Entity_proto_depIdxs, + MessageInfos: file_feast_core_Entity_proto_msgTypes, + }.Build() + File_feast_core_Entity_proto = out.File + file_feast_core_Entity_proto_rawDesc = nil + file_feast_core_Entity_proto_goTypes = nil + file_feast_core_Entity_proto_depIdxs = nil +} diff --git a/sdk/go/protos/feast/core/FeatureSet.pb.go b/sdk/go/protos/feast/core/FeatureSet.pb.go index b43b9f4ea77..d13041a8681 100644 --- a/sdk/go/protos/feast/core/FeatureSet.pb.go +++ b/sdk/go/protos/feast/core/FeatureSet.pb.go @@ -16,8 +16,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.24.0 -// protoc v3.10.0 +// protoc-gen-go v1.25.0 +// protoc v3.12.4 // source: feast/core/FeatureSet.proto package core diff --git a/sdk/go/protos/feast/core/FeatureSetReference.pb.go b/sdk/go/protos/feast/core/FeatureSetReference.pb.go index dfa47a25a7c..d82e94b6a1c 100644 --- a/sdk/go/protos/feast/core/FeatureSetReference.pb.go +++ b/sdk/go/protos/feast/core/FeatureSetReference.pb.go @@ -16,8 +16,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.24.0 -// protoc v3.10.0 +// protoc-gen-go v1.25.0 +// protoc v3.12.4 // source: feast/core/FeatureSetReference.proto package core diff --git a/sdk/go/protos/feast/core/IngestionJob.pb.go b/sdk/go/protos/feast/core/IngestionJob.pb.go index c8d3478ac9a..047a9cea0e6 100644 --- a/sdk/go/protos/feast/core/IngestionJob.pb.go +++ b/sdk/go/protos/feast/core/IngestionJob.pb.go @@ -16,8 +16,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.24.0 -// protoc v3.10.0 +// protoc-gen-go v1.25.0 +// protoc v3.12.4 // source: feast/core/IngestionJob.proto package core @@ -130,12 +130,12 @@ type IngestionJob struct { // 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"` + Stores []*Store `protobuf:"bytes,6,rep,name=stores,proto3" json:"stores,omitempty"` + // List of Feature Set References + FeatureSetReferences []*FeatureSetReference `protobuf:"bytes,7,rep,name=feature_set_references,json=featureSetReferences,proto3" json:"feature_set_references,omitempty"` } func (x *IngestionJob) Reset() { @@ -191,23 +191,23 @@ func (x *IngestionJob) GetStatus() IngestionJobStatus { return IngestionJobStatus_UNKNOWN } -func (x *IngestionJob) GetFeatureSets() []*FeatureSet { +func (x *IngestionJob) GetSource() *Source { if x != nil { - return x.FeatureSets + return x.Source } return nil } -func (x *IngestionJob) GetSource() *Source { +func (x *IngestionJob) GetStores() []*Store { if x != nil { - return x.Source + return x.Stores } return nil } -func (x *IngestionJob) GetStore() *Store { +func (x *IngestionJob) GetFeatureSetReferences() []*FeatureSetReference { if x != nil { - return x.Store + return x.FeatureSetReferences } return nil } @@ -339,62 +339,65 @@ 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, + 0x0a, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x1a, 0x24, 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, 0x22, 0x84, 0x01, 0x0a, 0x1a, 0x53, 0x70, 0x65, 0x63, 0x73, 0x53, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x12, 0x35, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 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, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x2f, 0x0a, 0x03, 0x61, 0x63, 0x6b, - 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, 0x52, 0x03, 0x61, 0x63, 0x6b, 0x22, 0x92, 0x01, 0x0a, 0x11, 0x46, - 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x53, 0x70, 0x65, 0x63, 0x41, 0x63, 0x6b, - 0x12, 0x32, 0x0a, 0x15, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x5f, - 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x13, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x66, 0x65, 0x72, - 0x65, 0x6e, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, - 0x73, 0x65, 0x74, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x11, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x56, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x6a, 0x6f, 0x62, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6a, 0x6f, 0x62, 0x4e, 0x61, 0x6d, 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, + 0x65, 0x74, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 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, 0xab, 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, 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, 0x29, 0x0a, 0x06, 0x73, 0x74, 0x6f, 0x72, + 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, + 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x06, 0x73, 0x74, 0x6f, + 0x72, 0x65, 0x73, 0x12, 0x55, 0x0a, 0x16, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, + 0x65, 0x74, 0x5f, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x07, 0x20, + 0x03, 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, 0x14, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, + 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, + 0x22, 0x84, 0x01, 0x0a, 0x1a, 0x53, 0x70, 0x65, 0x63, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, + 0x35, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 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, 0x52, 0x06, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x2f, 0x0a, 0x03, 0x61, 0x63, 0x6b, 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, 0x52, 0x03, 0x61, 0x63, 0x6b, 0x22, 0x92, 0x01, 0x0a, 0x11, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x53, 0x70, 0x65, 0x63, 0x41, 0x63, 0x6b, 0x12, 0x32, 0x0a, + 0x15, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x72, 0x65, 0x66, + 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x66, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, + 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x65, 0x74, + 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, + 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x6a, 0x6f, 0x62, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x6a, 0x6f, 0x62, 0x4e, 0x61, 0x6d, 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 ( @@ -416,16 +419,16 @@ var file_feast_core_IngestionJob_proto_goTypes = []interface{}{ (*IngestionJob)(nil), // 1: feast.core.IngestionJob (*SpecsStreamingUpdateConfig)(nil), // 2: feast.core.SpecsStreamingUpdateConfig (*FeatureSetSpecAck)(nil), // 3: feast.core.FeatureSetSpecAck - (*FeatureSet)(nil), // 4: feast.core.FeatureSet - (*Source)(nil), // 5: feast.core.Source - (*Store)(nil), // 6: feast.core.Store + (*Source)(nil), // 4: feast.core.Source + (*Store)(nil), // 5: feast.core.Store + (*FeatureSetReference)(nil), // 6: feast.core.FeatureSetReference (*KafkaSourceConfig)(nil), // 7: feast.core.KafkaSourceConfig } var file_feast_core_IngestionJob_proto_depIdxs = []int32{ 0, // 0: feast.core.IngestionJob.status:type_name -> feast.core.IngestionJobStatus - 4, // 1: feast.core.IngestionJob.feature_sets:type_name -> feast.core.FeatureSet - 5, // 2: feast.core.IngestionJob.source:type_name -> feast.core.Source - 6, // 3: feast.core.IngestionJob.store:type_name -> feast.core.Store + 4, // 1: feast.core.IngestionJob.source:type_name -> feast.core.Source + 5, // 2: feast.core.IngestionJob.stores:type_name -> feast.core.Store + 6, // 3: feast.core.IngestionJob.feature_set_references:type_name -> feast.core.FeatureSetReference 7, // 4: feast.core.SpecsStreamingUpdateConfig.source:type_name -> feast.core.KafkaSourceConfig 7, // 5: feast.core.SpecsStreamingUpdateConfig.ack:type_name -> feast.core.KafkaSourceConfig 6, // [6:6] is the sub-list for method output_type @@ -440,7 +443,7 @@ func file_feast_core_IngestionJob_proto_init() { if File_feast_core_IngestionJob_proto != nil { return } - file_feast_core_FeatureSet_proto_init() + file_feast_core_FeatureSetReference_proto_init() file_feast_core_Store_proto_init() file_feast_core_Source_proto_init() if !protoimpl.UnsafeEnabled { diff --git a/sdk/go/protos/feast/core/Runner.pb.go b/sdk/go/protos/feast/core/Runner.pb.go index 74dcf9fad77..105878c6d60 100644 --- a/sdk/go/protos/feast/core/Runner.pb.go +++ b/sdk/go/protos/feast/core/Runner.pb.go @@ -16,8 +16,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.24.0 -// protoc v3.10.0 +// protoc-gen-go v1.25.0 +// protoc v3.12.4 // source: feast/core/Runner.proto package core @@ -121,7 +121,7 @@ type DataflowRunnerConfigOptions struct { // 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"` + WorkerZone string `protobuf:"bytes,3,opt,name=workerZone,proto3" json:"workerZone,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. @@ -143,6 +143,14 @@ type DataflowRunnerConfigOptions struct { DeadLetterTableSpec string `protobuf:"bytes,12,opt,name=deadLetterTableSpec,proto3" json:"deadLetterTableSpec,omitempty"` // Labels to apply to the dataflow job Labels map[string]string `protobuf:"bytes,13,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // Disk size to use on each remote Compute Engine worker instance + DiskSizeGb int32 `protobuf:"varint,14,opt,name=diskSizeGb,proto3" json:"diskSizeGb,omitempty"` + // Run job on Dataflow Streaming Engine instead of creating worker VMs + EnableStreamingEngine bool `protobuf:"varint,15,opt,name=enableStreamingEngine,proto3" json:"enableStreamingEngine,omitempty"` + // Type of persistent disk to be used by workers + WorkerDiskType string `protobuf:"bytes,16,opt,name=workerDiskType,proto3" json:"workerDiskType,omitempty"` + // Kafka consumer configuration properties + KafkaConsumerProperties map[string]string `protobuf:"bytes,17,rep,name=kafkaConsumerProperties,proto3" json:"kafkaConsumerProperties,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *DataflowRunnerConfigOptions) Reset() { @@ -191,9 +199,9 @@ func (x *DataflowRunnerConfigOptions) GetRegion() string { return "" } -func (x *DataflowRunnerConfigOptions) GetZone() string { +func (x *DataflowRunnerConfigOptions) GetWorkerZone() string { if x != nil { - return x.Zone + return x.WorkerZone } return "" } @@ -268,6 +276,34 @@ func (x *DataflowRunnerConfigOptions) GetLabels() map[string]string { return nil } +func (x *DataflowRunnerConfigOptions) GetDiskSizeGb() int32 { + if x != nil { + return x.DiskSizeGb + } + return 0 +} + +func (x *DataflowRunnerConfigOptions) GetEnableStreamingEngine() bool { + if x != nil { + return x.EnableStreamingEngine + } + return false +} + +func (x *DataflowRunnerConfigOptions) GetWorkerDiskType() string { + if x != nil { + return x.WorkerDiskType + } + return "" +} + +func (x *DataflowRunnerConfigOptions) GetKafkaConsumerProperties() map[string]string { + if x != nil { + return x.KafkaConsumerProperties + } + return nil +} + var File_feast_core_Runner_proto protoreflect.FileDescriptor var file_feast_core_Runner_proto_rawDesc = []byte{ @@ -283,50 +319,71 @@ var file_feast_core_Runner_proto_rawDesc = []byte{ 0x64, 0x65, 0x61, 0x64, 0x4c, 0x65, 0x74, 0x74, 0x65, 0x72, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x70, 0x65, 0x63, 0x12, 0x22, 0x0a, 0x0c, 0x74, 0x65, 0x6d, 0x70, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x65, 0x6d, 0x70, 0x4c, - 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xcf, 0x04, 0x0a, 0x1b, 0x44, 0x61, 0x74, 0x61, + 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xa5, 0x07, 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, + 0x09, 0x52, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x77, 0x6f, 0x72, + 0x6b, 0x65, 0x72, 0x5a, 0x6f, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, + 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5a, 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, 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, 0x12, 0x4b, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x33, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 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, 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, 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, + 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, 0x12, 0x4b, 0x0a, + 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, + 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 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, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x69, + 0x73, 0x6b, 0x53, 0x69, 0x7a, 0x65, 0x47, 0x62, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, + 0x64, 0x69, 0x73, 0x6b, 0x53, 0x69, 0x7a, 0x65, 0x47, 0x62, 0x12, 0x34, 0x0a, 0x15, 0x65, 0x6e, + 0x61, 0x62, 0x6c, 0x65, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x67, + 0x69, 0x6e, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x65, 0x6e, 0x61, 0x62, 0x6c, + 0x65, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, + 0x12, 0x26, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x44, 0x69, 0x73, 0x6b, 0x54, 0x79, + 0x70, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, + 0x44, 0x69, 0x73, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x7e, 0x0a, 0x17, 0x6b, 0x61, 0x66, 0x6b, + 0x61, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, + 0x69, 0x65, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x44, 0x2e, 0x66, 0x65, 0x61, 0x73, + 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 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, 0x2e, 0x4b, 0x61, 0x66, 0x6b, 0x61, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, + 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x17, 0x6b, 0x61, 0x66, 0x6b, 0x61, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x50, 0x72, + 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 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, 0x1a, 0x4a, 0x0a, 0x1c, 0x4b, 0x61, 0x66, 0x6b, 0x61, 0x43, 0x6f, 0x6e, 0x73, + 0x75, 0x6d, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 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, + 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 ( @@ -341,19 +398,21 @@ func file_feast_core_Runner_proto_rawDescGZIP() []byte { return file_feast_core_Runner_proto_rawDescData } -var file_feast_core_Runner_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_feast_core_Runner_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_feast_core_Runner_proto_goTypes = []interface{}{ (*DirectRunnerConfigOptions)(nil), // 0: feast.core.DirectRunnerConfigOptions (*DataflowRunnerConfigOptions)(nil), // 1: feast.core.DataflowRunnerConfigOptions nil, // 2: feast.core.DataflowRunnerConfigOptions.LabelsEntry + nil, // 3: feast.core.DataflowRunnerConfigOptions.KafkaConsumerPropertiesEntry } var file_feast_core_Runner_proto_depIdxs = []int32{ 2, // 0: feast.core.DataflowRunnerConfigOptions.labels:type_name -> feast.core.DataflowRunnerConfigOptions.LabelsEntry - 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 + 3, // 1: feast.core.DataflowRunnerConfigOptions.kafkaConsumerProperties:type_name -> feast.core.DataflowRunnerConfigOptions.KafkaConsumerPropertiesEntry + 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_Runner_proto_init() } @@ -393,7 +452,7 @@ func file_feast_core_Runner_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_feast_core_Runner_proto_rawDesc, NumEnums: 0, - NumMessages: 3, + NumMessages: 4, NumExtensions: 0, NumServices: 0, }, diff --git a/sdk/go/protos/feast/core/Source.pb.go b/sdk/go/protos/feast/core/Source.pb.go index 2aa0c3f12e6..a5b3de95647 100644 --- a/sdk/go/protos/feast/core/Source.pb.go +++ b/sdk/go/protos/feast/core/Source.pb.go @@ -16,8 +16,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.24.0 -// protoc v3.10.0 +// protoc-gen-go v1.25.0 +// protoc v3.12.4 // source: feast/core/Source.proto package core diff --git a/sdk/go/protos/feast/core/Store.pb.go b/sdk/go/protos/feast/core/Store.pb.go index 9a339a6b173..c037fe84b53 100644 --- a/sdk/go/protos/feast/core/Store.pb.go +++ b/sdk/go/protos/feast/core/Store.pb.go @@ -16,8 +16,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.24.0 -// protoc v3.10.0 +// protoc-gen-go v1.25.0 +// protoc v3.12.4 // source: feast/core/Store.proto package core @@ -310,6 +310,8 @@ type Store_RedisConfig struct { 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"` + // Optional. How often flush data to redis + FlushFrequencySeconds int32 `protobuf:"varint,5,opt,name=flush_frequency_seconds,json=flushFrequencySeconds,proto3" json:"flush_frequency_seconds,omitempty"` } func (x *Store_RedisConfig) Reset() { @@ -372,17 +374,25 @@ func (x *Store_RedisConfig) GetMaxRetries() int32 { return 0 } +func (x *Store_RedisConfig) GetFlushFrequencySeconds() int32 { + if x != nil { + return x.FlushFrequencySeconds + } + return 0 +} + 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"` - WriteTriggeringFrequencySeconds int32 `protobuf:"varint,6,opt,name=write_triggering_frequency_seconds,json=writeTriggeringFrequencySeconds,proto3" json:"write_triggering_frequency_seconds,omitempty"` + 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"` + // Required. Frequency of running BQ load job and flushing all collected rows to BQ table + WriteTriggeringFrequencySeconds int32 `protobuf:"varint,6,opt,name=write_triggering_frequency_seconds,json=writeTriggeringFrequencySeconds,proto3" json:"write_triggering_frequency_seconds,omitempty"` } func (x *Store_BigQueryConfig) Reset() { @@ -523,6 +533,15 @@ type Store_RedisClusterConfig struct { 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"` + // Optional. How often flush data to redis + FlushFrequencySeconds int32 `protobuf:"varint,4,opt,name=flush_frequency_seconds,json=flushFrequencySeconds,proto3" json:"flush_frequency_seconds,omitempty"` + // Optional. Append a prefix to the Redis Key + KeyPrefix string `protobuf:"bytes,5,opt,name=key_prefix,json=keyPrefix,proto3" json:"key_prefix,omitempty"` + // Optional. Enable fallback to another key prefix if the original key is not present. + // Useful for migrating key prefix without re-ingestion. Disabled by default. + EnableFallback bool `protobuf:"varint,6,opt,name=enable_fallback,json=enableFallback,proto3" json:"enable_fallback,omitempty"` + // Optional. This would be the fallback prefix to use if enable_fallback is true. + FallbackPrefix string `protobuf:"bytes,7,opt,name=fallback_prefix,json=fallbackPrefix,proto3" json:"fallback_prefix,omitempty"` } func (x *Store_RedisClusterConfig) Reset() { @@ -578,6 +597,34 @@ func (x *Store_RedisClusterConfig) GetMaxRetries() int32 { return 0 } +func (x *Store_RedisClusterConfig) GetFlushFrequencySeconds() int32 { + if x != nil { + return x.FlushFrequencySeconds + } + return 0 +} + +func (x *Store_RedisClusterConfig) GetKeyPrefix() string { + if x != nil { + return x.KeyPrefix + } + return "" +} + +func (x *Store_RedisClusterConfig) GetEnableFallback() bool { + if x != nil { + return x.EnableFallback + } + return false +} + +func (x *Store_RedisClusterConfig) GetFallbackPrefix() string { + if x != nil { + return x.FallbackPrefix + } + return "" +} + type Store_Subscription struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -660,7 +707,7 @@ 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, 0x9b, 0x0a, 0x0a, 0x05, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x12, + 0x63, 0x6f, 0x72, 0x65, 0x22, 0xfc, 0x0b, 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, @@ -689,7 +736,7 @@ var file_feast_core_Store_proto_rawDesc = []byte{ 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, 0x1a, 0xbc, 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, @@ -697,57 +744,71 @@ var file_feast_core_Store_proto_rawDesc = []byte{ 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, 0xb9, 0x02, 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, 0x12, 0x4b, 0x0a, 0x22, 0x77, 0x72, - 0x69, 0x74, 0x65, 0x5f, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x66, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x1f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x54, 0x72, 0x69, - 0x67, 0x67, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x46, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, - 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, 0x5c, 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, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 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, + 0x0a, 0x6d, 0x61, 0x78, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x36, 0x0a, 0x17, 0x66, + 0x6c, 0x75, 0x73, 0x68, 0x5f, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x73, + 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x15, 0x66, 0x6c, + 0x75, 0x73, 0x68, 0x46, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x65, 0x63, 0x6f, + 0x6e, 0x64, 0x73, 0x1a, 0xb9, 0x02, 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, 0x12, 0x4b, 0x0a, 0x22, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x74, 0x72, 0x69, 0x67, + 0x67, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, + 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x1f, + 0x77, 0x72, 0x69, 0x74, 0x65, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x46, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 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, 0xb9, 0x02, 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, 0x12, 0x36, 0x0a, + 0x17, 0x66, 0x6c, 0x75, 0x73, 0x68, 0x5f, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, + 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x15, + 0x66, 0x6c, 0x75, 0x73, 0x68, 0x46, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x65, + 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x6b, 0x65, 0x79, 0x5f, 0x70, 0x72, 0x65, + 0x66, 0x69, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6b, 0x65, 0x79, 0x50, 0x72, + 0x65, 0x66, 0x69, 0x78, 0x12, 0x27, 0x0a, 0x0f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x66, + 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x65, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x27, 0x0a, + 0x0f, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, + 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x1a, 0x5c, 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, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 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 ( diff --git a/sdk/go/protos/feast/serving/ServingService.pb.go b/sdk/go/protos/feast/serving/ServingService.pb.go index 50e2f3fe2b6..32a663d9ca6 100644 --- a/sdk/go/protos/feast/serving/ServingService.pb.go +++ b/sdk/go/protos/feast/serving/ServingService.pb.go @@ -15,8 +15,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.24.0 -// protoc v3.10.0 +// protoc-gen-go v1.25.0 +// protoc v3.12.4 // source: feast/serving/ServingService.proto package serving @@ -24,6 +24,7 @@ package serving import ( context "context" 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" timestamp "github.com/golang/protobuf/ptypes/timestamp" grpc "google.golang.org/grpc" @@ -304,7 +305,7 @@ func (x GetOnlineFeaturesResponse_FieldStatus) Number() protoreflect.EnumNumber // Deprecated: Use GetOnlineFeaturesResponse_FieldStatus.Descriptor instead. func (GetOnlineFeaturesResponse_FieldStatus) EnumDescriptor() ([]byte, []int) { - return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{4, 0} + return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{5, 0} } type GetFeastServingInfoRequest struct { @@ -561,17 +562,22 @@ func (x *GetOnlineFeaturesRequest) GetProject() string { return "" } -type GetOnlineFeaturesResponse struct { +type GetBatchFeaturesRequest 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"` + // 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"` + // Compute statistics for the dataset retrieved + ComputeStatistics bool `protobuf:"varint,4,opt,name=compute_statistics,json=computeStatistics,proto3" json:"compute_statistics,omitempty"` } -func (x *GetOnlineFeaturesResponse) Reset() { - *x = GetOnlineFeaturesResponse{} +func (x *GetBatchFeaturesRequest) Reset() { + *x = GetBatchFeaturesRequest{} if protoimpl.UnsafeEnabled { mi := &file_feast_serving_ServingService_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -579,13 +585,13 @@ func (x *GetOnlineFeaturesResponse) Reset() { } } -func (x *GetOnlineFeaturesResponse) String() string { +func (x *GetBatchFeaturesRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetOnlineFeaturesResponse) ProtoMessage() {} +func (*GetBatchFeaturesRequest) ProtoMessage() {} -func (x *GetOnlineFeaturesResponse) ProtoReflect() protoreflect.Message { +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)) @@ -597,32 +603,43 @@ func (x *GetOnlineFeaturesResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetOnlineFeaturesResponse.ProtoReflect.Descriptor instead. -func (*GetOnlineFeaturesResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use GetBatchFeaturesRequest.ProtoReflect.Descriptor instead. +func (*GetBatchFeaturesRequest) Descriptor() ([]byte, []int) { return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{4} } -func (x *GetOnlineFeaturesResponse) GetFieldValues() []*GetOnlineFeaturesResponse_FieldValues { +func (x *GetBatchFeaturesRequest) GetFeatures() []*FeatureReference { if x != nil { - return x.FieldValues + return x.Features } return nil } -type GetBatchFeaturesRequest struct { +func (x *GetBatchFeaturesRequest) GetDatasetSource() *DatasetSource { + if x != nil { + return x.DatasetSource + } + return nil +} + +func (x *GetBatchFeaturesRequest) GetComputeStatistics() bool { + if x != nil { + return x.ComputeStatistics + } + return false +} + +type GetOnlineFeaturesResponse 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"` + // Feature values retrieved from feast. + FieldValues []*GetOnlineFeaturesResponse_FieldValues `protobuf:"bytes,1,rep,name=field_values,json=fieldValues,proto3" json:"field_values,omitempty"` } -func (x *GetBatchFeaturesRequest) Reset() { - *x = GetBatchFeaturesRequest{} +func (x *GetOnlineFeaturesResponse) Reset() { + *x = GetOnlineFeaturesResponse{} if protoimpl.UnsafeEnabled { mi := &file_feast_serving_ServingService_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -630,13 +647,13 @@ func (x *GetBatchFeaturesRequest) Reset() { } } -func (x *GetBatchFeaturesRequest) String() string { +func (x *GetOnlineFeaturesResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetBatchFeaturesRequest) ProtoMessage() {} +func (*GetOnlineFeaturesResponse) ProtoMessage() {} -func (x *GetBatchFeaturesRequest) ProtoReflect() protoreflect.Message { +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)) @@ -648,21 +665,14 @@ func (x *GetBatchFeaturesRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetBatchFeaturesRequest.ProtoReflect.Descriptor instead. -func (*GetBatchFeaturesRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use GetOnlineFeaturesResponse.ProtoReflect.Descriptor instead. +func (*GetOnlineFeaturesResponse) Descriptor() ([]byte, []int) { return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{5} } -func (x *GetBatchFeaturesRequest) GetFeatures() []*FeatureReference { - if x != nil { - return x.Features - } - return nil -} - -func (x *GetBatchFeaturesRequest) GetDatasetSource() *DatasetSource { +func (x *GetOnlineFeaturesResponse) GetFieldValues() []*GetOnlineFeaturesResponse_FieldValues { if x != nil { - return x.DatasetSource + return x.FieldValues } return nil } @@ -826,6 +836,9 @@ type Job struct { // 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"` + // Output only. The statistics computed over + // the retrieved dataset. Only available for BigQuery stores. + DatasetFeatureStatisticsList *v0.DatasetFeatureStatisticsList `protobuf:"bytes,7,opt,name=dataset_feature_statistics_list,json=datasetFeatureStatisticsList,proto3" json:"dataset_feature_statistics_list,omitempty"` } func (x *Job) Reset() { @@ -902,6 +915,13 @@ func (x *Job) GetDataFormat() DataFormat { return DataFormat_DATA_FORMAT_INVALID } +func (x *Job) GetDatasetFeatureStatisticsList() *v0.DatasetFeatureStatisticsList { + if x != nil { + return x.DatasetFeatureStatisticsList + } + return nil +} + type DatasetSource struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1068,7 +1088,7 @@ func (x *GetOnlineFeaturesResponse_FieldValues) ProtoReflect() protoreflect.Mess // Deprecated: Use GetOnlineFeaturesResponse_FieldValues.ProtoReflect.Descriptor instead. func (*GetOnlineFeaturesResponse_FieldValues) Descriptor() ([]byte, []int) { - return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{4, 0} + return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{5, 0} } func (x *GetOnlineFeaturesResponse_FieldValues) GetFields() map[string]*types.Value { @@ -1153,198 +1173,211 @@ var file_feast_serving_ServingService_proto_rawDesc = []byte{ 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, + 0x73, 0x2f, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2d, 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, 0x74, 0x61, 0x74, + 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 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, 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, 0xfb, 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, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 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, 0xdd, 0x04, 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, 0x89, 0x03, 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, 0x12, 0x5e, 0x0a, 0x08, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x42, 0x2e, 0x66, + 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, 0xfb, 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, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 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, 0xca, 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, 0x12, 0x2d, 0x0a, 0x12, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, + 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x11, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, + 0x73, 0x74, 0x69, 0x63, 0x73, 0x22, 0xdd, 0x04, 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, 0x89, 0x03, 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, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x08, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 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, 0x1a, 0x71, 0x0a, 0x0d, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x65, 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, 0x4a, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 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, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x5b, 0x0a, 0x0b, - 0x46, 0x69, 0x65, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x49, - 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x52, 0x45, 0x53, - 0x45, 0x4e, 0x54, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x4e, 0x55, 0x4c, 0x4c, 0x5f, 0x56, 0x41, - 0x4c, 0x55, 0x45, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x54, 0x5f, 0x46, 0x4f, 0x55, - 0x4e, 0x44, 0x10, 0x03, 0x12, 0x13, 0x0a, 0x0f, 0x4f, 0x55, 0x54, 0x53, 0x49, 0x44, 0x45, 0x5f, - 0x4d, 0x41, 0x58, 0x5f, 0x41, 0x47, 0x45, 0x10, 0x04, 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, 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, + 0x65, 0x73, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x5e, 0x0a, 0x08, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x42, 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, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x65, 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, 0x1a, 0x71, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, + 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, 0x4a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 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, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x5b, 0x0a, 0x0b, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x56, 0x41, 0x4c, + 0x49, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x52, 0x45, 0x53, 0x45, 0x4e, 0x54, 0x10, + 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x4e, 0x55, 0x4c, 0x4c, 0x5f, 0x56, 0x41, 0x4c, 0x55, 0x45, 0x10, + 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x54, 0x5f, 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x03, + 0x12, 0x13, 0x0a, 0x0f, 0x4f, 0x55, 0x54, 0x53, 0x49, 0x44, 0x45, 0x5f, 0x4d, 0x41, 0x58, 0x5f, + 0x41, 0x47, 0x45, 0x10, 0x04, 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, 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, + 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, 0xdf, 0x02, 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, - 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, + 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, 0x12, 0x7b, 0x0a, 0x1f, 0x64, + 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, + 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x0b, 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, 0x44, 0x61, + 0x74, 0x61, 0x73, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, + 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x1c, 0x64, 0x61, 0x74, 0x61, + 0x73, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, + 0x74, 0x69, 0x63, 0x73, 0x4c, 0x69, 0x73, 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, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, + 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, 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, 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, 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, + 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, - 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, + 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 ( @@ -1371,8 +1404,8 @@ var file_feast_serving_ServingService_proto_goTypes = []interface{}{ (*GetFeastServingInfoResponse)(nil), // 6: feast.serving.GetFeastServingInfoResponse (*FeatureReference)(nil), // 7: feast.serving.FeatureReference (*GetOnlineFeaturesRequest)(nil), // 8: feast.serving.GetOnlineFeaturesRequest - (*GetOnlineFeaturesResponse)(nil), // 9: feast.serving.GetOnlineFeaturesResponse - (*GetBatchFeaturesRequest)(nil), // 10: feast.serving.GetBatchFeaturesRequest + (*GetBatchFeaturesRequest)(nil), // 9: feast.serving.GetBatchFeaturesRequest + (*GetOnlineFeaturesResponse)(nil), // 10: feast.serving.GetOnlineFeaturesResponse (*GetBatchFeaturesResponse)(nil), // 11: feast.serving.GetBatchFeaturesResponse (*GetJobRequest)(nil), // 12: feast.serving.GetJobRequest (*GetJobResponse)(nil), // 13: feast.serving.GetJobResponse @@ -1381,47 +1414,49 @@ var file_feast_serving_ServingService_proto_goTypes = []interface{}{ (*GetOnlineFeaturesRequest_EntityRow)(nil), // 16: feast.serving.GetOnlineFeaturesRequest.EntityRow nil, // 17: feast.serving.GetOnlineFeaturesRequest.EntityRow.FieldsEntry (*GetOnlineFeaturesResponse_FieldValues)(nil), // 18: feast.serving.GetOnlineFeaturesResponse.FieldValues - nil, // 19: feast.serving.GetOnlineFeaturesResponse.FieldValues.FieldsEntry - nil, // 20: feast.serving.GetOnlineFeaturesResponse.FieldValues.StatusesEntry - (*DatasetSource_FileSource)(nil), // 21: feast.serving.DatasetSource.FileSource - (*timestamp.Timestamp)(nil), // 22: google.protobuf.Timestamp - (*types.Value)(nil), // 23: feast.types.Value + nil, // 19: feast.serving.GetOnlineFeaturesResponse.FieldValues.FieldsEntry + nil, // 20: feast.serving.GetOnlineFeaturesResponse.FieldValues.StatusesEntry + (*DatasetSource_FileSource)(nil), // 21: feast.serving.DatasetSource.FileSource + (*v0.DatasetFeatureStatisticsList)(nil), // 22: tensorflow.metadata.v0.DatasetFeatureStatisticsList + (*timestamp.Timestamp)(nil), // 23: google.protobuf.Timestamp + (*types.Value)(nil), // 24: feast.types.Value } var file_feast_serving_ServingService_proto_depIdxs = []int32{ 0, // 0: feast.serving.GetFeastServingInfoResponse.type:type_name -> feast.serving.FeastServingType 7, // 1: feast.serving.GetOnlineFeaturesRequest.features:type_name -> feast.serving.FeatureReference 16, // 2: feast.serving.GetOnlineFeaturesRequest.entity_rows:type_name -> feast.serving.GetOnlineFeaturesRequest.EntityRow - 18, // 3: feast.serving.GetOnlineFeaturesResponse.field_values:type_name -> feast.serving.GetOnlineFeaturesResponse.FieldValues - 7, // 4: feast.serving.GetBatchFeaturesRequest.features:type_name -> feast.serving.FeatureReference - 15, // 5: feast.serving.GetBatchFeaturesRequest.dataset_source:type_name -> feast.serving.DatasetSource + 7, // 3: feast.serving.GetBatchFeaturesRequest.features:type_name -> feast.serving.FeatureReference + 15, // 4: feast.serving.GetBatchFeaturesRequest.dataset_source:type_name -> feast.serving.DatasetSource + 18, // 5: feast.serving.GetOnlineFeaturesResponse.field_values:type_name -> feast.serving.GetOnlineFeaturesResponse.FieldValues 14, // 6: feast.serving.GetBatchFeaturesResponse.job:type_name -> feast.serving.Job 14, // 7: feast.serving.GetJobRequest.job:type_name -> feast.serving.Job 14, // 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 - 21, // 12: feast.serving.DatasetSource.file_source:type_name -> feast.serving.DatasetSource.FileSource - 22, // 13: feast.serving.GetOnlineFeaturesRequest.EntityRow.entity_timestamp:type_name -> google.protobuf.Timestamp - 17, // 14: feast.serving.GetOnlineFeaturesRequest.EntityRow.fields:type_name -> feast.serving.GetOnlineFeaturesRequest.EntityRow.FieldsEntry - 23, // 15: feast.serving.GetOnlineFeaturesRequest.EntityRow.FieldsEntry.value:type_name -> feast.types.Value - 19, // 16: feast.serving.GetOnlineFeaturesResponse.FieldValues.fields:type_name -> feast.serving.GetOnlineFeaturesResponse.FieldValues.FieldsEntry - 20, // 17: feast.serving.GetOnlineFeaturesResponse.FieldValues.statuses:type_name -> feast.serving.GetOnlineFeaturesResponse.FieldValues.StatusesEntry - 23, // 18: feast.serving.GetOnlineFeaturesResponse.FieldValues.FieldsEntry.value:type_name -> feast.types.Value - 4, // 19: feast.serving.GetOnlineFeaturesResponse.FieldValues.StatusesEntry.value:type_name -> feast.serving.GetOnlineFeaturesResponse.FieldStatus - 3, // 20: feast.serving.DatasetSource.FileSource.data_format:type_name -> feast.serving.DataFormat - 5, // 21: feast.serving.ServingService.GetFeastServingInfo:input_type -> feast.serving.GetFeastServingInfoRequest - 8, // 22: feast.serving.ServingService.GetOnlineFeatures:input_type -> feast.serving.GetOnlineFeaturesRequest - 10, // 23: feast.serving.ServingService.GetBatchFeatures:input_type -> feast.serving.GetBatchFeaturesRequest - 12, // 24: feast.serving.ServingService.GetJob:input_type -> feast.serving.GetJobRequest - 6, // 25: feast.serving.ServingService.GetFeastServingInfo:output_type -> feast.serving.GetFeastServingInfoResponse - 9, // 26: feast.serving.ServingService.GetOnlineFeatures:output_type -> feast.serving.GetOnlineFeaturesResponse - 11, // 27: feast.serving.ServingService.GetBatchFeatures:output_type -> feast.serving.GetBatchFeaturesResponse - 13, // 28: feast.serving.ServingService.GetJob:output_type -> feast.serving.GetJobResponse - 25, // [25:29] is the sub-list for method output_type - 21, // [21:25] is the sub-list for method input_type - 21, // [21:21] is the sub-list for extension type_name - 21, // [21:21] is the sub-list for extension extendee - 0, // [0:21] is the sub-list for field type_name + 22, // 12: feast.serving.Job.dataset_feature_statistics_list:type_name -> tensorflow.metadata.v0.DatasetFeatureStatisticsList + 21, // 13: feast.serving.DatasetSource.file_source:type_name -> feast.serving.DatasetSource.FileSource + 23, // 14: feast.serving.GetOnlineFeaturesRequest.EntityRow.entity_timestamp:type_name -> google.protobuf.Timestamp + 17, // 15: feast.serving.GetOnlineFeaturesRequest.EntityRow.fields:type_name -> feast.serving.GetOnlineFeaturesRequest.EntityRow.FieldsEntry + 24, // 16: feast.serving.GetOnlineFeaturesRequest.EntityRow.FieldsEntry.value:type_name -> feast.types.Value + 19, // 17: feast.serving.GetOnlineFeaturesResponse.FieldValues.fields:type_name -> feast.serving.GetOnlineFeaturesResponse.FieldValues.FieldsEntry + 20, // 18: feast.serving.GetOnlineFeaturesResponse.FieldValues.statuses:type_name -> feast.serving.GetOnlineFeaturesResponse.FieldValues.StatusesEntry + 24, // 19: feast.serving.GetOnlineFeaturesResponse.FieldValues.FieldsEntry.value:type_name -> feast.types.Value + 4, // 20: feast.serving.GetOnlineFeaturesResponse.FieldValues.StatusesEntry.value:type_name -> feast.serving.GetOnlineFeaturesResponse.FieldStatus + 3, // 21: feast.serving.DatasetSource.FileSource.data_format:type_name -> feast.serving.DataFormat + 5, // 22: feast.serving.ServingService.GetFeastServingInfo:input_type -> feast.serving.GetFeastServingInfoRequest + 8, // 23: feast.serving.ServingService.GetOnlineFeatures:input_type -> feast.serving.GetOnlineFeaturesRequest + 9, // 24: feast.serving.ServingService.GetBatchFeatures:input_type -> feast.serving.GetBatchFeaturesRequest + 12, // 25: feast.serving.ServingService.GetJob:input_type -> feast.serving.GetJobRequest + 6, // 26: feast.serving.ServingService.GetFeastServingInfo:output_type -> feast.serving.GetFeastServingInfoResponse + 10, // 27: feast.serving.ServingService.GetOnlineFeatures:output_type -> feast.serving.GetOnlineFeaturesResponse + 11, // 28: feast.serving.ServingService.GetBatchFeatures:output_type -> feast.serving.GetBatchFeaturesResponse + 13, // 29: feast.serving.ServingService.GetJob:output_type -> feast.serving.GetJobResponse + 26, // [26:30] is the sub-list for method output_type + 22, // [22:26] is the sub-list for method input_type + 22, // [22:22] is the sub-list for extension type_name + 22, // [22:22] is the sub-list for extension extendee + 0, // [0:22] is the sub-list for field type_name } func init() { file_feast_serving_ServingService_proto_init() } @@ -1479,7 +1514,7 @@ func file_feast_serving_ServingService_proto_init() { } } file_feast_serving_ServingService_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetOnlineFeaturesResponse); i { + switch v := v.(*GetBatchFeaturesRequest); i { case 0: return &v.state case 1: @@ -1491,7 +1526,7 @@ func file_feast_serving_ServingService_proto_init() { } } file_feast_serving_ServingService_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetBatchFeaturesRequest); i { + switch v := v.(*GetOnlineFeaturesResponse); i { case 0: return &v.state case 1: diff --git a/sdk/go/protos/feast/storage/Redis.pb.go b/sdk/go/protos/feast/storage/Redis.pb.go index 92c1040ca5a..b75f6085470 100644 --- a/sdk/go/protos/feast/storage/Redis.pb.go +++ b/sdk/go/protos/feast/storage/Redis.pb.go @@ -15,8 +15,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.24.0 -// protoc v3.10.0 +// protoc-gen-go v1.25.0 +// protoc v3.12.4 // source: feast/storage/Redis.proto package storage diff --git a/sdk/go/protos/feast/types/FeatureRow.pb.go b/sdk/go/protos/feast/types/FeatureRow.pb.go index 4c094379eb1..0c61f25f3d0 100644 --- a/sdk/go/protos/feast/types/FeatureRow.pb.go +++ b/sdk/go/protos/feast/types/FeatureRow.pb.go @@ -15,8 +15,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.24.0 -// protoc v3.10.0 +// protoc-gen-go v1.25.0 +// protoc v3.12.4 // source: feast/types/FeatureRow.proto package types diff --git a/sdk/go/protos/feast/types/FeatureRowExtended.pb.go b/sdk/go/protos/feast/types/FeatureRowExtended.pb.go index 3cb15a21ce0..d1e02b1e0ae 100644 --- a/sdk/go/protos/feast/types/FeatureRowExtended.pb.go +++ b/sdk/go/protos/feast/types/FeatureRowExtended.pb.go @@ -15,8 +15,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.24.0 -// protoc v3.10.0 +// protoc-gen-go v1.25.0 +// protoc v3.12.4 // source: feast/types/FeatureRowExtended.proto package types diff --git a/sdk/go/protos/feast/types/Field.pb.go b/sdk/go/protos/feast/types/Field.pb.go index 08b35f66272..9dad77cdb91 100644 --- a/sdk/go/protos/feast/types/Field.pb.go +++ b/sdk/go/protos/feast/types/Field.pb.go @@ -15,8 +15,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.24.0 -// protoc v3.10.0 +// protoc-gen-go v1.25.0 +// protoc v3.12.4 // source: feast/types/Field.proto package types diff --git a/sdk/go/protos/feast/types/Value.pb.go b/sdk/go/protos/feast/types/Value.pb.go index bb777a07661..3b194356331 100644 --- a/sdk/go/protos/feast/types/Value.pb.go +++ b/sdk/go/protos/feast/types/Value.pb.go @@ -15,8 +15,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.24.0 -// protoc v3.10.0 +// protoc-gen-go v1.25.0 +// protoc v3.12.4 // source: feast/types/Value.proto package types diff --git a/sdk/go/protos/tensorflow_metadata/proto/v0/path.pb.go b/sdk/go/protos/tensorflow_metadata/proto/v0/path.pb.go index f58247299d9..1daa7687f94 100644 --- a/sdk/go/protos/tensorflow_metadata/proto/v0/path.pb.go +++ b/sdk/go/protos/tensorflow_metadata/proto/v0/path.pb.go @@ -15,8 +15,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.24.0 -// protoc v3.10.0 +// protoc-gen-go v1.25.0 +// protoc v3.12.4 // source: tensorflow_metadata/proto/v0/path.proto package v0 diff --git a/sdk/go/protos/tensorflow_metadata/proto/v0/schema.pb.go b/sdk/go/protos/tensorflow_metadata/proto/v0/schema.pb.go index a04f5bba8ca..940779a1917 100644 --- a/sdk/go/protos/tensorflow_metadata/proto/v0/schema.pb.go +++ b/sdk/go/protos/tensorflow_metadata/proto/v0/schema.pb.go @@ -15,8 +15,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.24.0 -// protoc v3.10.0 +// protoc-gen-go v1.25.0 +// protoc v3.12.4 // source: tensorflow_metadata/proto/v0/schema.proto package v0 diff --git a/sdk/go/protos/tensorflow_metadata/proto/v0/statistics.pb.go b/sdk/go/protos/tensorflow_metadata/proto/v0/statistics.pb.go index 3d9e7da362d..fbf6247a1d3 100644 --- a/sdk/go/protos/tensorflow_metadata/proto/v0/statistics.pb.go +++ b/sdk/go/protos/tensorflow_metadata/proto/v0/statistics.pb.go @@ -19,8 +19,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.24.0 -// protoc v3.10.0 +// protoc-gen-go v1.25.0 +// protoc v3.12.4 // source: tensorflow_metadata/proto/v0/statistics.proto package v0 diff --git a/sdk/python/feast/cli.py b/sdk/python/feast/cli.py index aaea3ff8b45..2c6e6fced37 100644 --- a/sdk/python/feast/cli.py +++ b/sdk/python/feast/cli.py @@ -25,6 +25,7 @@ from feast.config import Config from feast.contrib.job_controller.client import Client as JCClient from feast.core.IngestionJob_pb2 import IngestionJobStatus +from feast.entity import EntityV2 from feast.feature_set import FeatureSet, FeatureSetRef from feast.loaders.yaml import yaml_loader @@ -114,6 +115,99 @@ def config_set(prop, value): sys.exit(1) +@cli.group(name="entities") +def entity(): + """ + Create and manage entities + """ + pass + + +@entity.command("apply") +@click.option( + "--filename", + "-f", + help="Path to an entity configuration file that will be applied", + type=click.Path(exists=True), +) +@click.option( + "--project", + "-p", + help="Project that entity belongs to", + type=click.STRING, + default="default", +) +def entity_create(filename, project): + """ + Create or update an entity + """ + + entities = [ + EntityV2.from_dict(entity_dict) for entity_dict in yaml_loader(filename) + ] + feast_client = Client() # type: Client + feast_client.apply_entity(entities, project) + + +@entity.command("describe") +@click.argument("name", type=click.STRING) +@click.option( + "--project", + "-p", + help="Project that entity belongs to", + type=click.STRING, + default="default", +) +def entity_describe(name: str, project: str): + """ + Describe an entity + """ + feast_client = Client() # type: Client + entity = feast_client.get_entity(name=name, project=project) + + if not entity: + print(f'Entity with name "{name}" could not be found') + return + + print( + yaml.dump( + yaml.safe_load(str(entity)), default_flow_style=False, sort_keys=False + ) + ) + + +@entity.command(name="list") +@click.option( + "--project", + "-p", + help="Project that entity belongs to", + type=click.STRING, + default="", +) +@click.option( + "--labels", + "-l", + help="Labels to filter for entities", + type=click.STRING, + default="", +) +def entity_list(project: str, labels: str): + """ + List all entities + """ + feast_client = Client() # type: Client + + labels_dict = _get_labels_dict(labels) + + table = [] + for entity in feast_client.list_entities(project=project, labels=labels_dict): + table.append([entity.name, entity.description, entity.value_type]) + + from tabulate import tabulate + + print(tabulate(table, headers=["NAME", "DESCRIPTION", "TYPE"], tablefmt="plain")) + + @cli.group(name="features") def feature(): """ diff --git a/sdk/python/feast/client.py b/sdk/python/feast/client.py index 8d49e95ed67..6076be7dbef 100644 --- a/sdk/python/feast/client.py +++ b/sdk/python/feast/client.py @@ -19,7 +19,6 @@ import tempfile import time import uuid -from collections import OrderedDict from math import ceil from typing import Any, Dict, List, Optional, Tuple, Union, cast @@ -43,16 +42,22 @@ FEAST_DEFAULT_OPTIONS, ) from feast.core.CoreService_pb2 import ( + ApplyEntityRequest, + ApplyEntityResponse, ApplyFeatureSetRequest, ApplyFeatureSetResponse, ArchiveProjectRequest, ArchiveProjectResponse, CreateProjectRequest, CreateProjectResponse, + GetEntityRequest, + GetEntityResponse, GetFeastCoreVersionRequest, GetFeatureSetRequest, GetFeatureSetResponse, GetFeatureStatisticsRequest, + ListEntitiesRequest, + ListEntitiesResponse, ListFeatureSetsRequest, ListFeatureSetsResponse, ListFeaturesRequest, @@ -62,8 +67,9 @@ ) from feast.core.CoreService_pb2_grpc import CoreServiceStub from feast.core.FeatureSet_pb2 import FeatureSetStatus +from feast.entity import EntityV2 from feast.feature import Feature, FeatureRef -from feast.feature_set import Entity, FeatureSet +from feast.feature_set import FeatureSet from feast.grpc import auth as feast_auth from feast.grpc.grpc import create_grpc_channel from feast.job import RetrievalJob @@ -287,6 +293,8 @@ def project(self) -> Union[str, None]: Returns: Project name """ + if not self._config.get(CONFIG_PROJECT_KEY): + raise ValueError("No project has been configured.") return self._config.get(CONFIG_PROJECT_KEY) def set_project(self, project: Optional[str] = None): @@ -353,6 +361,130 @@ def archive_project(self, project): if self._project == project: self._project = FEAST_DEFAULT_OPTIONS[CONFIG_PROJECT_KEY] + def apply_entity( + self, entities: Union[List[EntityV2], EntityV2], project: str = None + ): + """ + Idempotently registers entities with Feast Core. Either a single + entity or a list can be provided. + + Args: + entities: List of entities that will be registered + + Examples: + >>> from feast import Client + >>> from feast.entity import EntityV2 + >>> from feast.value_type import ValueType + >>> + >>> feast_client = Client(core_url="localhost:6565") + >>> entity = EntityV2( + >>> name="driver_entity", + >>> description="Driver entity for car rides", + >>> value_type=ValueType.STRING, + >>> labels={ + >>> "key": "val" + >>> } + >>> ) + >>> feast_client.apply_entity(entity) + """ + + if project is None: + project = self.project + + if not isinstance(entities, list): + entities = [entities] + for entity in entities: + if isinstance(entity, EntityV2): + self._apply_entity(project, entity) # type: ignore + continue + raise ValueError(f"Could not determine entity type to apply {entity}") + + def _apply_entity(self, project: str, entity: EntityV2): + """ + Registers a single entity with Feast + + Args: + entity: Entity that will be registered + """ + + entity.is_valid() + entity_proto = entity.to_spec_proto() + + # Convert the entity to a request and send to Feast Core + try: + apply_entity_response = self._core_service.ApplyEntity( + ApplyEntityRequest(project=project, spec=entity_proto), # type: ignore + timeout=self._config.getint(CONFIG_GRPC_CONNECTION_TIMEOUT_DEFAULT_KEY), + metadata=self._get_grpc_metadata(), + ) # type: ApplyEntityResponse + except grpc.RpcError as e: + raise grpc.RpcError(e.details()) + + # Extract the returned entity + applied_entity = EntityV2.from_proto(apply_entity_response.entity) + + # Deep copy from the returned entity to the local entity + entity._update_from_entity(applied_entity) + + def list_entities( + self, project: str = None, labels: Dict[str, str] = dict() + ) -> List[EntityV2]: + """ + Retrieve a list of entities from Feast Core + + Args: + project: Filter entities based on project name + labels: User-defined labels that these entities are associated with + + Returns: + List of entities + """ + + if project is None: + project = self.project + + filter = ListEntitiesRequest.Filter(project=project, labels=labels) + + # Get latest entities from Feast Core + entity_protos = self._core_service.ListEntities( + ListEntitiesRequest(filter=filter), metadata=self._get_grpc_metadata(), + ) # type: ListEntitiesResponse + + # Extract entities and return + entities = [] + for entity_proto in entity_protos.entities: + entity = EntityV2.from_proto(entity_proto) + entity._client = self + entities.append(entity) + return entities + + def get_entity(self, name: str, project: str = None) -> Union[EntityV2, None]: + """ + Retrieves an entity. + + Args: + project: Feast project that this entity belongs to + name: Name of entity + + Returns: + Returns either the specified entity, or raises an exception if + none is found + """ + + if project is None: + project = self.project + + try: + get_entity_response = self._core_service.GetEntity( + GetEntityRequest(project=project, name=name.strip()), + metadata=self._get_grpc_metadata(), + ) # type: GetEntityResponse + except grpc.RpcError as e: + raise grpc.RpcError(e.details()) + entity = EntityV2.from_proto(get_entity_response.entity) + + return entity + def apply(self, feature_sets: Union[List[FeatureSet], FeatureSet]): """ Idempotently registers feature set(s) with Feast Core. Either a single @@ -528,18 +660,6 @@ def list_features_by_ref( return features_dict - def list_entities(self) -> Dict[str, Entity]: - """ - Returns a dictionary of entities across all feature sets - Returns: - Dictionary of entities, indexed by name - """ - entities_dict = OrderedDict() - for fs in self.list_feature_sets(): - for entity in fs.entities: - entities_dict[entity.name] = entity - return entities_dict - def get_historical_features( self, feature_refs: List[str], diff --git a/sdk/python/feast/entity.py b/sdk/python/feast/entity.py index 012d01631af..caa8b22f78e 100644 --- a/sdk/python/feast/entity.py +++ b/sdk/python/feast/entity.py @@ -12,8 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Dict, MutableMapping, Optional + +import yaml +from google.protobuf import json_format +from google.protobuf.json_format import MessageToDict, MessageToJson +from google.protobuf.timestamp_pb2 import Timestamp + +from feast.core.Entity_pb2 import Entity as EntityV2Proto +from feast.core.Entity_pb2 import EntityMeta as EntityMetaProto +from feast.core.Entity_pb2 import EntitySpecV2 as EntitySpecProto from feast.core.FeatureSet_pb2 import EntitySpec as EntityProto from feast.field import Field +from feast.loaders import yaml as feast_yaml from feast.types import Value_pb2 as ValueTypeProto from feast.value_type import ValueType @@ -44,3 +55,273 @@ def from_proto(cls, entity_proto: EntityProto): """ entity = cls(name=entity_proto.name, dtype=ValueType(entity_proto.value_type)) return entity + + +class EntityV2: + """ + Represents a collection of entities and associated metadata. + """ + + def __init__( + self, + name: str, + description: str, + value_type: ValueType, + labels: Optional[MutableMapping[str, str]] = None, + ): + self._name = name + self._description = description + self._value_type = value_type + if labels is None: + self._labels = dict() # type: MutableMapping[str, str] + else: + self._labels = labels + + self._created_timestamp: Optional[Timestamp] = None + self._last_updated_timestamp: Optional[Timestamp] = None + + def __eq__(self, other): + if not isinstance(other, EntityV2): + raise TypeError("Comparisons should only involve EntityV2 class objects.") + + if isinstance(self.value_type, int): + self.value_type = ValueType(self.value_type).name + if isinstance(other.value_type, int): + other.value_type = ValueType(other.value_type).name + + if ( + self.labels != other.labels + or self.name != other.name + or self.description != other.description + or self.value_type != other.value_type + ): + return False + + return True + + def __str__(self): + return str(MessageToJson(self.to_proto())) + + @property + def name(self): + """ + Returns the name of this entity + """ + return self._name + + @name.setter + def name(self, name): + """ + Sets the name of this entity + """ + self._name = name + + @property + def description(self): + """ + Returns the description of this entity + """ + return self._description + + @description.setter + def description(self, description): + """ + Sets the description of this entity + """ + self._description = description + + @property + def value_type(self): + """ + Returns the type of this entity + """ + return self._value_type + + @value_type.setter + def value_type(self, value_type: ValueType): + """ + Set the type for this entity + """ + self._value_type = value_type + + @property + def labels(self): + """ + Returns the labels of this entity. This is the user defined metadata + defined as a dictionary. + """ + return self._labels + + @labels.setter + def labels(self, labels: MutableMapping[str, str]): + """ + Set the labels for this entity + """ + self._labels = labels + + @property + def created_timestamp(self): + """ + Returns the created_timestamp of this entity + """ + return self._created_timestamp + + @property + def last_updated_timestamp(self): + """ + Returns the last_updated_timestamp of this entity + """ + return self._last_updated_timestamp + + def is_valid(self): + """ + Validates the state of a entity locally. Raises an exception + if entity is invalid. + """ + + if not self.name: + raise ValueError("No name found in entity.") + + if not self.value_type: + raise ValueError("No type found in entity {self.value_type}") + + @classmethod + def from_yaml(cls, yml: str): + """ + Creates an entity from a YAML string body or a file path + + Args: + yml: Either a file path containing a yaml file or a YAML string + + Returns: + Returns a EntityV2 object based on the YAML file + """ + + return cls.from_dict(feast_yaml.yaml_loader(yml, load_single=True)) + + @classmethod + def from_dict(cls, entity_dict): + """ + Creates an entity from a dict + + Args: + entity_dict: A dict representation of an entity + + Returns: + Returns a EntityV2 object based on the entity dict + """ + + entity_proto = json_format.ParseDict( + entity_dict, EntityV2Proto(), ignore_unknown_fields=True + ) + return cls.from_proto(entity_proto) + + @classmethod + def from_proto(cls, entity_proto: EntityV2Proto): + """ + Creates an entity from a protobuf representation of an entity + + Args: + entity_proto: A protobuf representation of an entity + + Returns: + Returns a EntityV2 object based on the entity protobuf + """ + + entity = cls( + name=entity_proto.spec.name, + description=entity_proto.spec.description, + value_type=ValueType(entity_proto.spec.value_type).name, # type: ignore + labels=entity_proto.spec.labels, + ) + + entity._created_timestamp = entity_proto.meta.created_timestamp + entity._last_updated_timestamp = entity_proto.meta.last_updated_timestamp + + return entity + + def to_proto(self) -> EntityV2Proto: + """ + Converts an entity object to its protobuf representation + + Returns: + EntityV2Proto protobuf + """ + + meta = EntityMetaProto( + created_timestamp=self.created_timestamp, + last_updated_timestamp=self.last_updated_timestamp, + ) + if isinstance(self.value_type, ValueType): + self.value_type = self.value_type.value + + spec = EntitySpecProto( + name=self.name, + description=self.description, + value_type=self.value_type, + labels=self.labels, + ) + + return EntityV2Proto(spec=spec, meta=meta) + + def to_dict(self) -> Dict: + """ + Converts entity to dict + + Returns: + Dictionary object representation of entity + """ + + entity_dict = MessageToDict(self.to_proto()) + + # Remove meta when empty for more readable exports + if entity_dict["meta"] == {}: + del entity_dict["meta"] + + return entity_dict + + def to_yaml(self): + """ + Converts a entity to a YAML string. + + Returns: + Entity string returned in YAML format + """ + entity_dict = self.to_dict() + return yaml.dump(entity_dict, allow_unicode=True, sort_keys=False) + + def to_spec_proto(self) -> EntitySpecProto: + """ + Converts an EntityV2 object to its protobuf representation. + Used when passing EntitySpecV2 object to Feast request. + + Returns: + EntitySpecV2 protobuf + """ + + if isinstance(self.value_type, ValueType): + self.value_type = self.value_type.value + + spec = EntitySpecProto( + name=self.name, + description=self.description, + value_type=self.value_type, + labels=self.labels, + ) + + return spec + + def _update_from_entity(self, entity): + """ + Deep replaces one entity with another + + Args: + entity: Entity to use as a source of configuration + """ + + self.name = entity.name + self.description = entity.description + self.value_type = entity.value_type + self.labels = entity.labels + self._created_timestamp = entity.created_timestamp + self._last_updated_timestamp = entity.last_updated_timestamp diff --git a/sdk/python/tests/test_entity.py b/sdk/python/tests/test_entity.py new file mode 100644 index 00000000000..4d146da729f --- /dev/null +++ b/sdk/python/tests/test_entity.py @@ -0,0 +1,86 @@ +# 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 socket +from concurrent import futures +from contextlib import closing + +import grpc +import pytest + +from feast.client import Client +from feast.core import CoreService_pb2_grpc as Core +from feast.entity import EntityV2 +from feast.value_type import ValueType +from feast_core_server import CoreServicer + + +def find_free_port(): + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: + s.bind(("", 0)) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + return s.getsockname()[1] + + +free_port = find_free_port() + + +class TestEntity: + @pytest.fixture(scope="function") + def server(self): + server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) + Core.add_CoreServiceServicer_to_server(CoreServicer(), server) + server.add_insecure_port(f"[::]:{free_port}") + server.start() + yield server + server.stop(0) + + @pytest.fixture + def client(self, server): + return Client(core_url=f"localhost:{free_port}") + + def test_entity_import_export_yaml(self): + + test_entity = EntityV2( + name="car_driver_entity", + description="Driver entity for car rides", + value_type=ValueType.STRING, + labels={"team": "matchmaking"}, + ) + + # Create a string YAML representation of the entity + string_yaml = test_entity.to_yaml() + + # Create a new entity object from the YAML string + actual_entity_from_string = EntityV2.from_yaml(string_yaml) + + # Ensure equality is upheld to original entity + assert test_entity == actual_entity_from_string + + +def test_entity_class_contains_labels(): + entity = EntityV2( + "my-entity", + description="My entity", + value_type=ValueType.STRING, + labels={"key1": "val1", "key2": "val2"}, + ) + assert "key1" in entity.labels.keys() and entity.labels["key1"] == "val1" + assert "key2" in entity.labels.keys() and entity.labels["key2"] == "val2" + + +def test_entity_without_labels_empty_dict(): + entity = EntityV2("my-entity", description="My entity", value_type=ValueType.STRING) + assert entity.labels == dict() + assert len(entity.labels) == 0 From 442ca5a1a448032d7096747160ea05f94aaaa7f5 Mon Sep 17 00:00:00 2001 From: Zhu Zhan Yan Date: Fri, 2 Oct 2020 09:44:07 +0800 Subject: [PATCH 27/36] Add Feature Tables API to Core & Python SDK (#1019) * Reorganise existing protos in CoreService by type. Signed-off-by: Terence * Add new FeatureTables API to Core Protobuf definitions Signed-off-by: Terence * Fix name collision in proto java outer classname with message name Signed-off-by: Terence * Add missing max age field to Feature Table proto. Signed-off-by: Terence * Add Flyway DB migration to add Feature Table API. Signed-off-by: Terence * Rename options field to options_json and change type to text. * Options to be stored as Protobuf JSON. * Change from varchar to text to remove char limit Signed-off-by: Terence * FeatureTable: Rename entity_names to entities Signed-off-by: Terence * Revert Reorganise existing protos in CoreService by type as it make it hard for reviewers to review changes Signed-off-by: Terence * Add FeatureSource entity for native representation of FeatureSource protobuf Signed-off-by: Terence * Add missing nullable annotation on FeatureSource entity. Signed-off-by: Terence * Update ListFeatureTablesRequest's Filter to follow naming convention. Signed-off-by: Terence * Add missing serialization code for FeatureSource's field mapping. Signed-off-by: Terence * Split Feature proto from FeatureTable proto. Signed-off-by: Terence * Update FeatureTable entity_names field to entities Signed-off-by: Terence * Revert putting project in feature table spec Signed-off-by: Terence * Update ListFeatureTable Proto to return full FeatureTable objects and limit to listing from one Project. Signed-off-by: Terence * Fix typo in CoreService proto Signed-off-by: Terence * Add FeatureV2 core model to store FeatureSpecV2 Signed-off-by: Terence * Add FeatureTable core model to store FeatureTable protos Signed-off-by: Terence * Fix naming grammar in CoreService proto Signed-off-by: Terence * Standardise naming of specifying projects in CoreService proto Signed-off-by: Terence * Rename FeatureSource proto to FeatureSourceSpec for compatiblity. Signed-off-by: Terence * Update FeatureSource model to store type specific options as seperate columns instead of JSON. Signed-off-by: Terence * Add FeatureTableTest unit test to test FeatureTable core model Signed-off-by: Terence * Add FeatureTableValidator to validate FeatureTableSpec protobufs Signed-off-by: Terence * Add listFeatureTables(), applyFeatureTable() & getFeatureTable() to Core's SpecService Signed-off-by: Terence * Add FeatureTableRepository to save & retrieve FeatureTables in database. Signed-off-by: Terence * Fix hibernate errors on Feast Core boot. Signed-off-by: Terence * Implement listFeatureTables() , applyFeatureTable(), and getFeatureTable() in CoreServiceImpl Signed-off-by: Terence * Add applyFeatureSet integration tests SpecServiceIT Signed-off-by: Terence * Various fixes for creating FeatureTabes with applyFeatureTable Signed-off-by: Terence * Fixed bug with updating FeatureTable Signed-off-by: Terence * Update ListFeatureTables Signed-off-by: Terence * Add Python SDK Signed-off-by: Terence * Update GetFeatureTable Signed-off-by: Terence * Remove unused proto imports and generate go protos Signed-off-by: Terence * Fix ListFeatureTables IT Signed-off-by: Terence * Update comment to generalize FeatureSource's field mapping to all fields instead of just for feature. Signed-off-by: Terence * Fix feature table validator condition Signed-off-by: Terence * Fix feature table unit tests Signed-off-by: Terence * Update feature source proto Signed-off-by: Terence * Address PR comments Signed-off-by: Terence * Replace test with IT Signed-off-by: Terence * Update IT config Signed-off-by: Terence * Fix removal of entity check Signed-off-by: Terence * Fix test sort issue Signed-off-by: Terence * Store source options as json Signed-off-by: Terence * Update go protos Signed-off-by: Terence * Remove go FeatureSource proto Signed-off-by: Terence * Increase IT max pool size Signed-off-by: Terence * Reduce pool size instead Signed-off-by: Terence * Replace mutablemapping with dict Signed-off-by: Terence * Standardize use of timestamp_column instead of ts_column Signed-off-by: Terence Co-authored-by: Terence Lim --- .../java/feast/common/it/DataGenerator.java | 78 + .../feast/common/it/SimpleCoreClient.java | 28 + .../main/java/feast/common/util/TestUtil.java | 34 + .../core/dao/FeatureTableRepository.java | 31 + .../java/feast/core/grpc/CoreServiceImpl.java | 104 +- .../java/feast/core/model/DataSource.java | 192 ++ .../java/feast/core/model/FeatureTable.java | 321 ++++ .../main/java/feast/core/model/FeatureV2.java | 129 ++ .../main/java/feast/core/model/Project.java | 8 + .../java/feast/core/service/SpecService.java | 136 +- .../validators/FeatureTableValidator.java | 81 + .../java/feast/core/validators/Matchers.java | 20 + .../db/migration/V2.8__Feature_Tables_API.sql | 66 + .../feast/core/auth/CoreServiceAuthTest.java | 4 +- .../java/feast/core/model/DataSourceTest.java | 75 + .../feast/core/service/SpecServiceIT.java | 433 ++++- .../validators/FeatureTableValidatorTest.java | 69 + .../test/resources/application-it.properties | 2 +- go.mod | 2 +- go.sum | 6 + protos/feast/core/CoreService.proto | 69 +- protos/feast/core/DataSource.proto | 103 ++ protos/feast/core/Entity.proto | 3 +- protos/feast/core/Feature.proto | 36 + protos/feast/core/FeatureTable.proto | 79 + sdk/go/protos/feast/core/CoreService.pb.go | 1572 ++++++++++++----- sdk/go/protos/feast/core/DataSource.pb.go | 709 ++++++++ sdk/go/protos/feast/core/Feature.pb.go | 205 +++ sdk/go/protos/feast/core/FeatureTable.pb.go | 450 +++++ sdk/python/feast/cli.py | 83 + sdk/python/feast/client.py | 120 ++ sdk/python/feast/data_source.py | 512 ++++++ sdk/python/feast/feature_table.py | 434 +++++ sdk/python/feast/feature_v2.py | 94 + sdk/python/tests/test_feature_table.py | 101 ++ 35 files changed, 5913 insertions(+), 476 deletions(-) create mode 100644 core/src/main/java/feast/core/dao/FeatureTableRepository.java create mode 100644 core/src/main/java/feast/core/model/DataSource.java create mode 100644 core/src/main/java/feast/core/model/FeatureTable.java create mode 100644 core/src/main/java/feast/core/model/FeatureV2.java create mode 100644 core/src/main/java/feast/core/validators/FeatureTableValidator.java create mode 100644 core/src/main/resources/db/migration/V2.8__Feature_Tables_API.sql create mode 100644 core/src/test/java/feast/core/model/DataSourceTest.java create mode 100644 core/src/test/java/feast/core/validators/FeatureTableValidatorTest.java create mode 100644 protos/feast/core/DataSource.proto create mode 100644 protos/feast/core/Feature.proto create mode 100644 protos/feast/core/FeatureTable.proto create mode 100644 sdk/go/protos/feast/core/DataSource.pb.go create mode 100644 sdk/go/protos/feast/core/Feature.pb.go create mode 100644 sdk/go/protos/feast/core/FeatureTable.pb.go create mode 100644 sdk/python/feast/data_source.py create mode 100644 sdk/python/feast/feature_table.py create mode 100644 sdk/python/feast/feature_v2.py create mode 100644 sdk/python/tests/test_feature_table.py diff --git a/common-test/src/main/java/feast/common/it/DataGenerator.java b/common-test/src/main/java/feast/common/it/DataGenerator.java index 6c5428ff171..236e516432e 100644 --- a/common-test/src/main/java/feast/common/it/DataGenerator.java +++ b/common-test/src/main/java/feast/common/it/DataGenerator.java @@ -17,8 +17,16 @@ package feast.common.it; import com.google.common.collect.ImmutableList; +import com.google.protobuf.Duration; +import feast.proto.core.DataSourceProto.DataSource; +import feast.proto.core.DataSourceProto.DataSource.BigQueryOptions; +import feast.proto.core.DataSourceProto.DataSource.FileOptions; +import feast.proto.core.DataSourceProto.DataSource.KafkaOptions; import feast.proto.core.EntityProto; +import feast.proto.core.FeatureProto; +import feast.proto.core.FeatureProto.FeatureSpecV2; import feast.proto.core.FeatureSetProto; +import feast.proto.core.FeatureTableProto.FeatureTableSpec; import feast.proto.core.SourceProto; import feast.proto.core.StoreProto; import feast.proto.types.ValueProto; @@ -130,6 +138,15 @@ public static EntityProto.EntitySpecV2 createEntitySpecV2( .build(); } + public static FeatureProto.FeatureSpecV2 createFeatureSpecV2( + String name, ValueProto.ValueType.Enum valueType, Map labels) { + return FeatureProto.FeatureSpecV2.newBuilder() + .setName(name) + .setValueType(valueType) + .putAllLabels(labels) + .build(); + } + public static FeatureSetProto.FeatureSet createFeatureSet( SourceProto.Source source, String projectName, @@ -193,4 +210,65 @@ public static FeatureSetProto.FeatureSet createFeatureSet( return createFeatureSet( source, projectName, name, Collections.emptyMap(), Collections.emptyMap()); } + + // Create a Feature Table spec without DataSources configured. + public static FeatureTableSpec createFeatureTableSpec( + String name, + List entities, + Map features, + int maxAgeSecs, + Map labels) { + + return FeatureTableSpec.newBuilder() + .setName(name) + .addAllEntities(entities) + .addAllFeatures( + features.entrySet().stream() + .map( + entry -> + FeatureSpecV2.newBuilder() + .setName(entry.getKey()) + .setValueType(entry.getValue()) + .putAllLabels(labels) + .build()) + .collect(Collectors.toList())) + .setMaxAge(Duration.newBuilder().setSeconds(3600).build()) + .putAllLabels(labels) + .build(); + } + + public static DataSource createFileDataSourceSpec( + String fileURL, String fileFormat, String timestampColumn, String datePartitionColumn) { + return DataSource.newBuilder() + .setType(DataSource.SourceType.BATCH_FILE) + .setFileOptions( + FileOptions.newBuilder().setFileFormat(fileFormat).setFileUrl(fileURL).build()) + .setTimestampColumn(timestampColumn) + .setDatePartitionColumn(datePartitionColumn) + .build(); + } + + public static DataSource createBigQueryDataSourceSpec( + String bigQueryTableRef, String timestampColumn, String datePartitionColumn) { + return DataSource.newBuilder() + .setType(DataSource.SourceType.BATCH_BIGQUERY) + .setBigqueryOptions(BigQueryOptions.newBuilder().setTableRef(bigQueryTableRef).build()) + .setTimestampColumn(timestampColumn) + .setDatePartitionColumn(datePartitionColumn) + .build(); + } + + public static DataSource createKafkaDataSourceSpec( + String servers, String topic, String classPath, String timestampColumn) { + return DataSource.newBuilder() + .setType(DataSource.SourceType.STREAM_KAFKA) + .setKafkaOptions( + KafkaOptions.newBuilder() + .setTopic(topic) + .setBootstrapServers(servers) + .setClassPath(classPath) + .build()) + .setTimestampColumn(timestampColumn) + .build(); + } } diff --git a/common-test/src/main/java/feast/common/it/SimpleCoreClient.java b/common-test/src/main/java/feast/common/it/SimpleCoreClient.java index 6f01d83bb63..b54dbfc8578 100644 --- a/common-test/src/main/java/feast/common/it/SimpleCoreClient.java +++ b/common-test/src/main/java/feast/common/it/SimpleCoreClient.java @@ -17,6 +17,8 @@ package feast.common.it; import feast.proto.core.*; +import feast.proto.core.CoreServiceProto.ApplyFeatureTableRequest; +import feast.proto.core.FeatureTableProto.FeatureTableSpec; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -75,6 +77,13 @@ public List simpleListEntities( .getEntitiesList(); } + public List simpleListFeatureTables( + CoreServiceProto.ListFeatureTablesRequest.Filter filter) { + return stub.listFeatureTables( + CoreServiceProto.ListFeatureTablesRequest.newBuilder().setFilter(filter).build()) + .getTablesList(); + } + public List simpleListFeatureSets( String projectName, String featureSetName, Map labels) { return stub.listFeatureSets( @@ -131,6 +140,15 @@ public EntityProto.Entity simpleGetEntity(String projectName, String name) { .getEntity(); } + public FeatureTableProto.FeatureTable simpleGetFeatureTable(String projectName, String name) { + return stub.getFeatureTable( + CoreServiceProto.GetFeatureTableRequest.newBuilder() + .setName(name) + .setProject(projectName) + .build()) + .getTable(); + } + public void updateFeatureSetStatus( String projectName, String name, FeatureSetProto.FeatureSetStatus status) { stub.updateFeatureSetStatus( @@ -190,4 +208,14 @@ public FeatureSetProto.FeatureSet getFeatureSet(String projectName, String featu .build()) .getFeatureSet(); } + + public FeatureTableProto.FeatureTable applyFeatureTable( + String projectName, FeatureTableSpec spec) { + return stub.applyFeatureTable( + ApplyFeatureTableRequest.newBuilder() + .setProject(projectName) + .setTableSpec(spec) + .build()) + .getTable(); + } } diff --git a/common-test/src/main/java/feast/common/util/TestUtil.java b/common-test/src/main/java/feast/common/util/TestUtil.java index 1931de6a5dc..142c5e4850d 100644 --- a/common-test/src/main/java/feast/common/util/TestUtil.java +++ b/common-test/src/main/java/feast/common/util/TestUtil.java @@ -21,6 +21,10 @@ import feast.common.logging.AuditLogger; import feast.common.logging.config.LoggingProperties; +import feast.proto.core.FeatureProto.FeatureSpecV2; +import feast.proto.core.FeatureTableProto.FeatureTableSpec; +import java.util.Comparator; +import java.util.stream.Collectors; import org.springframework.boot.info.BuildProperties; public class TestUtil { @@ -37,4 +41,34 @@ public static void setupAuditLogger() { new AuditLogger(loggingProperties, buildProperties); } + + /** + * Compare if two Feature Table specs are equal. Disregards order of features/entities in spec. + */ + public static boolean compareFeatureTableSpec(FeatureTableSpec spec, FeatureTableSpec otherSpec) { + spec = + spec.toBuilder() + .clearFeatures() + .addAllFeatures( + spec.getFeaturesList().stream() + .sorted(Comparator.comparing(FeatureSpecV2::getName)) + .collect(Collectors.toSet())) + .clearEntities() + .addAllEntities(spec.getEntitiesList().stream().sorted().collect(Collectors.toSet())) + .build(); + + otherSpec = + otherSpec + .toBuilder() + .clearFeatures() + .addAllFeatures( + spec.getFeaturesList().stream() + .sorted(Comparator.comparing(FeatureSpecV2::getName)) + .collect(Collectors.toSet())) + .clearEntities() + .addAllEntities(spec.getEntitiesList().stream().sorted().collect(Collectors.toSet())) + .build(); + + return spec.equals(otherSpec); + } } diff --git a/core/src/main/java/feast/core/dao/FeatureTableRepository.java b/core/src/main/java/feast/core/dao/FeatureTableRepository.java new file mode 100644 index 00000000000..2d125345a92 --- /dev/null +++ b/core/src/main/java/feast/core/dao/FeatureTableRepository.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.core.dao; + +import feast.core.model.FeatureTable; +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +/** JPA repository for querying FeatureTables stored. */ +public interface FeatureTableRepository extends JpaRepository { + // Find single FeatureTable by project and name + Optional findFeatureTableByNameAndProject_Name(String name, String projectName); + + // Find FeatureTables by project + List findAllByProject_Name(String projectName); +} diff --git a/core/src/main/java/feast/core/grpc/CoreServiceImpl.java b/core/src/main/java/feast/core/grpc/CoreServiceImpl.java index 3a3106ee1bc..ef7218d13f5 100644 --- a/core/src/main/java/feast/core/grpc/CoreServiceImpl.java +++ b/core/src/main/java/feast/core/grpc/CoreServiceImpl.java @@ -34,6 +34,7 @@ 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 net.devh.boot.grpc.server.service.GrpcService; @@ -285,8 +286,8 @@ public void applyFeatureSet( String projectId = null; try { - FeatureSet featureSet = specService.imputeProjectName(request.getFeatureSet()); - projectId = featureSet.getSpec().getProject(); + FeatureSet featureSet = request.getFeatureSet(); + projectId = SpecService.resolveProjectName(featureSet.getSpec().getProject()); authorizationService.authorizeRequest(SecurityContextHolder.getContext(), projectId); ApplyFeatureSetResponse response = specService.applyFeatureSet(featureSet); responseObserver.onNext(response); @@ -391,4 +392,103 @@ public void listProjects( Status.INTERNAL.withDescription(e.getMessage()).withCause(e).asRuntimeException()); } } + + @Override + public void applyFeatureTable( + ApplyFeatureTableRequest request, + StreamObserver responseObserver) { + String projectName = SpecService.resolveProjectName(request.getProject()); + String tableName = request.getTableSpec().getName(); + + try { + // Check if user has authorization to apply feature table + authorizationService.authorizeRequest(SecurityContextHolder.getContext(), projectName); + + ApplyFeatureTableResponse response = specService.applyFeatureTable(request); + responseObserver.onNext(response); + responseObserver.onCompleted(); + } catch (AccessDeniedException e) { + log.info( + String.format( + "ApplyFeatureTable: Not authorized to access project to apply: %s", projectName)); + responseObserver.onError( + Status.PERMISSION_DENIED + .withDescription(e.getMessage()) + .withCause(e) + .asRuntimeException()); + } catch (org.hibernate.exception.ConstraintViolationException e) { + log.error( + String.format( + "ApplyFeatureTable: Unable to apply Feature Table due to a conflict: " + + "Ensure that name is unique within Project: (name: %s, project: %s)", + projectName, tableName)); + responseObserver.onError( + Status.ALREADY_EXISTS.withDescription(e.getMessage()).withCause(e).asRuntimeException()); + } catch (IllegalArgumentException e) { + log.error( + String.format( + "ApplyFeatureTable: Invalid apply Feature Table Request: (name: %s, project: %s)", + projectName, tableName)); + responseObserver.onError( + Status.INVALID_ARGUMENT + .withDescription(e.getMessage()) + .withCause(e) + .asRuntimeException()); + } catch (UnsupportedOperationException e) { + log.error( + String.format( + "ApplyFeatureTable: Unsupported apply Feature Table Request: (name: %s, project: %s)", + projectName, tableName)); + responseObserver.onError( + Status.UNIMPLEMENTED.withDescription(e.getMessage()).withCause(e).asRuntimeException()); + } catch (Exception e) { + log.error("ApplyFeatureTable Exception has occurred:", e); + responseObserver.onError( + Status.INTERNAL.withDescription(e.getMessage()).withCause(e).asRuntimeException()); + } + } + + @Override + public void listFeatureTables( + ListFeatureTablesRequest request, + StreamObserver responseObserver) { + try { + ListFeatureTablesResponse response = specService.listFeatureTables(request.getFilter()); + responseObserver.onNext(response); + responseObserver.onCompleted(); + } catch (IllegalArgumentException e) { + log.error(String.format("ListFeatureTable: Invalid list Feature Table Request")); + responseObserver.onError( + Status.INVALID_ARGUMENT + .withDescription(e.getMessage()) + .withCause(e) + .asRuntimeException()); + } catch (Exception e) { + log.error("ListFeatureTable: Exception has occurred: ", e); + responseObserver.onError( + Status.INTERNAL.withDescription(e.getMessage()).withCause(e).asRuntimeException()); + } + } + + @Override + public void getFeatureTable( + GetFeatureTableRequest request, StreamObserver responseObserver) { + try { + GetFeatureTableResponse response = specService.getFeatureTable(request); + + responseObserver.onNext(response); + responseObserver.onCompleted(); + } catch (NoSuchElementException e) { + log.error( + String.format( + "GetFeatureTable: No such Feature Table: (project: %s, name: %s)", + request.getProject(), request.getName())); + responseObserver.onError( + Status.NOT_FOUND.withDescription(e.getMessage()).withCause(e).asRuntimeException()); + } catch (Exception e) { + log.error("GetFeatureTable: Exception has occurred: ", e); + responseObserver.onError( + Status.INTERNAL.withDescription(e.getMessage()).withCause(e).asRuntimeException()); + } + } } diff --git a/core/src/main/java/feast/core/model/DataSource.java b/core/src/main/java/feast/core/model/DataSource.java new file mode 100644 index 00000000000..a72906c0737 --- /dev/null +++ b/core/src/main/java/feast/core/model/DataSource.java @@ -0,0 +1,192 @@ +/* + * 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 feast.proto.core.DataSourceProto.DataSource.SourceType.*; + +import feast.core.util.TypeConversion; +import feast.proto.core.DataSourceProto; +import feast.proto.core.DataSourceProto.DataSource.BigQueryOptions; +import feast.proto.core.DataSourceProto.DataSource.FileOptions; +import feast.proto.core.DataSourceProto.DataSource.KafkaOptions; +import feast.proto.core.DataSourceProto.DataSource.KinesisOptions; +import feast.proto.core.DataSourceProto.DataSource.SourceType; +import java.util.HashMap; +import java.util.Map; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; + +@Entity +@Getter +@Setter(AccessLevel.PRIVATE) +@Table(name = "data_sources") +public class DataSource { + @Column(name = "id") + @Id + @GeneratedValue + private long id; + + // Type of this Data Source + @Enumerated(EnumType.STRING) + @Column(name = "type", nullable = false) + private SourceType type; + + // DataSource Options + @Column(name = "config") + private String configJSON; + + // Field mapping between sourced fields (key) and feature fields (value). + // Stored as serialized JSON string. + @Column(name = "field_mapping", columnDefinition = "text") + private String fieldMapJSON; + + @Column(name = "timestamp_column") + private String timestampColumn; + + @Column(name = "date_partition_column") + private String datePartitionColumn; + + public DataSource() {}; + + public DataSource(SourceType type) { + this.type = type; + } + + /** + * Construct a DataSource from the given Protobuf representation spec + * + * @param spec Protobuf representation of DataSource to construct from. + * @throws IllegalArgumentException when provided with a invalid Protobuf spec + * @throws UnsupportedOperationException if source type is unsupported. + */ + public static DataSource fromProto(DataSourceProto.DataSource spec) { + DataSource source = new DataSource(spec.getType()); + // Copy source type specific options + Map dataSourceConfigMap = new HashMap<>(); + switch (spec.getType()) { + case BATCH_FILE: + dataSourceConfigMap.put("file_url", spec.getFileOptions().getFileUrl()); + dataSourceConfigMap.put("file_format", spec.getFileOptions().getFileFormat()); + break; + case BATCH_BIGQUERY: + dataSourceConfigMap.put("table_ref", spec.getBigqueryOptions().getTableRef()); + break; + case STREAM_KAFKA: + dataSourceConfigMap.put("bootstrap_servers", spec.getKafkaOptions().getBootstrapServers()); + dataSourceConfigMap.put("class_path", spec.getKafkaOptions().getClassPath()); + dataSourceConfigMap.put("topic", spec.getKafkaOptions().getTopic()); + break; + case STREAM_KINESIS: + dataSourceConfigMap.put("class_path", spec.getKinesisOptions().getClassPath()); + dataSourceConfigMap.put("region", spec.getKinesisOptions().getRegion()); + dataSourceConfigMap.put("stream_name", spec.getKinesisOptions().getStreamName()); + break; + default: + throw new UnsupportedOperationException( + String.format("Unsupported Feature Store Type: %s", spec.getType())); + } + + // Store DataSource mapping as serialised JSON + source.setConfigJSON(TypeConversion.convertMapToJsonString(dataSourceConfigMap)); + + // Store field mapping as serialised JSON + source.setFieldMapJSON(TypeConversion.convertMapToJsonString(spec.getFieldMappingMap())); + + // Set timestamp mapping columns + source.setTimestampColumn(spec.getTimestampColumn()); + source.setDatePartitionColumn(spec.getDatePartitionColumn()); + + return source; + } + + /** Convert this DataSource to its Protobuf representation. */ + public DataSourceProto.DataSource toProto() { + DataSourceProto.DataSource.Builder spec = DataSourceProto.DataSource.newBuilder(); + spec.setType(getType()); + + // Extract source type specific options + Map dataSourceConfigMap = + TypeConversion.convertJsonStringToMap(getConfigJSON()); + switch (getType()) { + case BATCH_FILE: + FileOptions.Builder fileOptions = FileOptions.newBuilder(); + fileOptions.setFileUrl(dataSourceConfigMap.get("file_url")); + fileOptions.setFileFormat(dataSourceConfigMap.get("file_format")); + spec.setFileOptions(fileOptions.build()); + break; + case BATCH_BIGQUERY: + BigQueryOptions.Builder bigQueryOptions = BigQueryOptions.newBuilder(); + bigQueryOptions.setTableRef(dataSourceConfigMap.get("table_ref")); + spec.setBigqueryOptions(bigQueryOptions.build()); + break; + case STREAM_KAFKA: + KafkaOptions.Builder kafkaOptions = KafkaOptions.newBuilder(); + kafkaOptions.setBootstrapServers(dataSourceConfigMap.get("bootstrap_servers")); + kafkaOptions.setClassPath(dataSourceConfigMap.get("class_path")); + kafkaOptions.setTopic(dataSourceConfigMap.get("topic")); + spec.setKafkaOptions(kafkaOptions.build()); + break; + case STREAM_KINESIS: + KinesisOptions.Builder kinesisOptions = KinesisOptions.newBuilder(); + kinesisOptions.setClassPath(dataSourceConfigMap.get("class_path")); + kinesisOptions.setRegion(dataSourceConfigMap.get("region")); + kinesisOptions.setStreamName(dataSourceConfigMap.get("stream_name")); + spec.setKinesisOptions(kinesisOptions.build()); + break; + default: + throw new UnsupportedOperationException( + String.format("Unsupported Feature Store Type: %s", getType())); + } + + // Parse field mapping and options from JSON + spec.putAllFieldMapping(TypeConversion.convertJsonStringToMap(getFieldMapJSON())); + + spec.setTimestampColumn(getTimestampColumn()); + spec.setDatePartitionColumn(getDatePartitionColumn()); + + return spec.build(); + } + + public Map getFieldsMap() { + return TypeConversion.convertJsonStringToMap(getFieldMapJSON()); + } + + @Override + public int hashCode() { + return toProto().hashCode(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DataSource other = (DataSource) o; + return this.toProto().equals(other.toProto()); + } +} diff --git a/core/src/main/java/feast/core/model/FeatureTable.java b/core/src/main/java/feast/core/model/FeatureTable.java new file mode 100644 index 00000000000..6d9c4146442 --- /dev/null +++ b/core/src/main/java/feast/core/model/FeatureTable.java @@ -0,0 +1,321 @@ +/* + * 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 com.google.protobuf.Duration; +import com.google.protobuf.Timestamp; +import feast.core.dao.EntityRepository; +import feast.core.util.TypeConversion; +import feast.proto.core.DataSourceProto; +import feast.proto.core.FeatureProto.FeatureSpecV2; +import feast.proto.core.FeatureTableProto; +import feast.proto.core.FeatureTableProto.FeatureTableSpec; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Entity +@Setter(AccessLevel.PRIVATE) +@Table( + name = "feature_tables", + uniqueConstraints = @UniqueConstraint(columnNames = {"name", "project_name"})) +public class FeatureTable extends AbstractTimestampEntity { + + @Id @GeneratedValue private long id; + + // Name of Feature Table + @Column(name = "name", nullable = false) + private String name; + + // Name of the Project that this FeatureTable belongs to + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "project_name") + private Project project; + + // Features defined in this Feature Table + @OneToMany( + mappedBy = "featureTable", + cascade = CascadeType.ALL, + fetch = FetchType.EAGER, + orphanRemoval = true) + private Set features; + + // Entites to associate the features defined in this FeatureTable with + @ManyToMany + @JoinTable( + name = "feature_tables_entities_v2", + joinColumns = @JoinColumn(name = "feature_table_id"), + inverseJoinColumns = @JoinColumn(name = "entity_v2_id")) + private Set entities; + + // User defined metadata labels serialized as JSON string. + @Column(name = "labels", columnDefinition = "text") + private String labelsJSON; + + // Max Age of the Features defined in this Feature Table in seconds + @Column(name = "max_age", nullable = false) + private long maxAgeSecs; + + // Streaming DataSource used to obtain data for features from a stream + @OneToOne(cascade = CascadeType.ALL) + @JoinColumn(name = "stream_source_id", nullable = true) + private DataSource streamSource; + + // Batched DataSource used to obtain data for features from a batch of data + @OneToOne(cascade = CascadeType.ALL) + @JoinColumn(name = "batch_source_id", nullable = false) + private DataSource batchSource; + + // Autoincrementing version no. of this FeatureTable. + // Autoincrements every update made to the FeatureTable. + @Column(name = "revision", nullable = false) + private int revision; + + public FeatureTable() {}; + + /** + * Construct FeatureTable from Protobuf spec representation in the given project with entities + * registered in entity repository. + * + * @param projectName the name of the project that the constructed FeatureTable belongs. + * @param spec the Protobuf spec to construct the Feature from. + * @param entityRepo {@link EntityRepository} used to resolve entity names. + * @throws IllegalArgumentException if the Protobuf spec provided is invalid. + * @return constructed FeatureTable from the given Protobuf spec. + */ + public static FeatureTable fromProto( + String projectName, FeatureTableSpec spec, EntityRepository entityRepo) { + FeatureTable table = new FeatureTable(); + table.setName(spec.getName()); + table.setProject(new Project(projectName)); + + Set features = + spec.getFeaturesList().stream() + .map(featureSpec -> FeatureV2.fromProto(table, featureSpec)) + .collect(Collectors.toSet()); + table.setFeatures(features); + + Set entities = + FeatureTable.resolveEntities( + projectName, spec.getName(), entityRepo, spec.getEntitiesList()); + table.setEntities(entities); + + String labelsJSON = TypeConversion.convertMapToJsonString(spec.getLabelsMap()); + table.setLabelsJSON(labelsJSON); + + table.setMaxAgeSecs(spec.getMaxAge().getSeconds()); + table.setBatchSource(DataSource.fromProto(spec.getBatchSource())); + + // Configure stream source only if set + if (!spec.getStreamSource().equals(DataSourceProto.DataSource.getDefaultInstance())) { + table.setStreamSource(DataSource.fromProto(spec.getStreamSource())); + } + + return table; + } + + /** + * Update the FeatureTable from the given Protobuf representation. + * + * @param spec the Protobuf spec to update the FeatureTable from. + * @throws IllegalArgumentException if the update will make prohibited changes. + */ + public void updateFromProto(FeatureTableSpec spec) { + // Check for prohibited changes made in spec: + // - Name cannot be changed + if (!getName().equals(spec.getName())) { + throw new IllegalArgumentException( + String.format( + "Updating the name of a registered FeatureTable is not allowed: %s to %s", + getName(), spec.getName())); + } + // - Entities cannot be changed + List entityNames = + getEntities().stream().map(EntityV2::getName).collect(Collectors.toList()); + if (!new HashSet<>(entityNames).equals(new HashSet<>(spec.getEntitiesList()))) { + Collections.sort(entityNames); + throw new IllegalArgumentException( + String.format( + "Updating the entities of a registered FeatureTable is not allowed: %s to %s", + entityNames, spec.getEntitiesList())); + } + + // Update FeatureTable based on spec + // Update existing features, create new feature, drop missing features + Map existingFeatures = + getFeatures().stream().collect(Collectors.toMap(FeatureV2::getName, feature -> feature)); + this.features.clear(); + this.features.addAll( + spec.getFeaturesList().stream() + .map( + featureSpec -> { + if (!existingFeatures.containsKey(featureSpec.getName())) { + // Create new Feature based on spec + return FeatureV2.fromProto(this, featureSpec); + } + // Update existing feature based on spec + FeatureV2 feature = existingFeatures.get(featureSpec.getName()); + feature.updateFromProto(featureSpec); + return feature; + }) + .collect(Collectors.toSet())); + + this.maxAgeSecs = spec.getMaxAge().getSeconds(); + this.labelsJSON = TypeConversion.convertMapToJsonString(spec.getLabelsMap()); + + this.batchSource = DataSource.fromProto(spec.getBatchSource()); + if (!spec.getStreamSource().equals(DataSourceProto.DataSource.getDefaultInstance())) { + this.streamSource = DataSource.fromProto(spec.getStreamSource()); + } else { + this.streamSource = null; + } + + // Bump revision no. + this.revision++; + } + + /** Convert this Feature Table to its Protobuf representation */ + public FeatureTableProto.FeatureTable toProto() { + // Convert field types to Protobuf compatible types + Timestamp creationTime = TypeConversion.convertTimestamp(getCreated()); + Timestamp updatedTime = TypeConversion.convertTimestamp(getLastUpdated()); + + List featureSpecs = + getFeatures().stream().map(FeatureV2::toProto).collect(Collectors.toList()); + List entityNames = + getEntities().stream().map(EntityV2::getName).collect(Collectors.toList()); + Map labels = TypeConversion.convertJsonStringToMap(getLabelsJSON()); + + FeatureTableSpec.Builder spec = + FeatureTableSpec.newBuilder() + .setName(getName()) + .setMaxAge(Duration.newBuilder().setSeconds(getMaxAgeSecs()).build()) + .setBatchSource(getBatchSource().toProto()) + .addAllEntities(entityNames) + .addAllFeatures(featureSpecs) + .putAllLabels(labels); + if (getStreamSource() != null) { + spec.setStreamSource(getStreamSource().toProto()); + } + + return FeatureTableProto.FeatureTable.newBuilder() + .setMeta( + FeatureTableProto.FeatureTableMeta.newBuilder() + .setRevision(getRevision()) + .setCreatedTimestamp(creationTime) + .setLastUpdatedTimestamp(updatedTime) + .build()) + .setSpec(spec.build()) + .build(); + } + + /** Use given entity repository to resolve entity names to entity native objects */ + private static Set resolveEntities( + String projectName, String tableName, EntityRepository repo, Collection names) { + return names.stream() + .map( + entityName -> { + EntityV2 entity = repo.findEntityByNameAndProject_Name(entityName, projectName); + if (entity == null) { + throw new IllegalArgumentException( + String.format( + "Feature Table refers to no existent Entity: (table: %s, entity: %s, project: %s)", + tableName, entityName, projectName)); + } + return entity; + }) + .collect(Collectors.toSet()); + } + + /** + * Determine whether a FeatureTable has all the specified labels. + * + * @param labelsFilter labels contain key-value mapping for labels attached to the FeatureTable + * @return boolean True if Entity contains all labels in the labelsFilter + */ + public boolean hasAllLabels(Map labelsFilter) { + Map LabelsMap = this.getLabelsMap(); + for (String key : labelsFilter.keySet()) { + if (!LabelsMap.containsKey(key) || !LabelsMap.get(key).equals(labelsFilter.get(key))) { + return false; + } + } + return true; + } + + public Map getLabelsMap() { + return TypeConversion.convertJsonStringToMap(getLabelsJSON()); + } + + @Override + public int hashCode() { + return Objects.hash( + getName(), + getProject(), + getFeatures(), + getEntities(), + getMaxAgeSecs(), + getBatchSource(), + getStreamSource()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof FeatureTable)) { + return false; + } + + FeatureTable other = (FeatureTable) o; + + return getName().equals(other.getName()) + && getProject().equals(other.getProject()) + && getLabelsJSON().equals(other.getLabelsJSON()) + && getFeatures().containsAll(other.getFeatures()) + && getEntities().containsAll(other.getEntities()) + && getMaxAgeSecs() == getMaxAgeSecs() + && Optional.ofNullable(getBatchSource()).equals(Optional.ofNullable(other.getBatchSource())) + && Optional.ofNullable(getStreamSource()) + .equals(Optional.ofNullable(other.getStreamSource())); + } +} diff --git a/core/src/main/java/feast/core/model/FeatureV2.java b/core/src/main/java/feast/core/model/FeatureV2.java new file mode 100644 index 00000000000..ee969ece991 --- /dev/null +++ b/core/src/main/java/feast/core/model/FeatureV2.java @@ -0,0 +1,129 @@ +/* + * 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.core.util.TypeConversion; +import feast.proto.core.FeatureProto.FeatureSpecV2; +import feast.proto.types.ValueProto.ValueType; +import java.util.Map; +import java.util.Objects; +import javax.persistence.*; +import javax.persistence.Entity; +import lombok.Getter; + +/** Defines a single Feature defined in a {@link FeatureTable} */ +@Getter +@Entity +@Table( + name = "features_v2", + uniqueConstraints = @UniqueConstraint(columnNames = {"name", "feature_table_id"})) +public class FeatureV2 { + @Id @GeneratedValue private long id; + + // Name of the Feature + private String name; + + // Feature Table where this Feature is defined in. + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "feature_table_id", nullable = false) + private FeatureTable featureTable; + + // Value type of the feature. String representation of ValueType. + @Enumerated(EnumType.STRING) + @Column(name = "type") + private ValueType.Enum type; + + // User defined metadata labels for this feature encoded a JSON string. + @Column(name = "labels", columnDefinition = "text") + private String labelsJSON; + + public FeatureV2() {}; + + public FeatureV2(FeatureTable table, String name, ValueType.Enum type, String labelsJSON) { + this.featureTable = table; + this.name = name; + this.type = type; + this.labelsJSON = labelsJSON; + } + + /** + * Construct Feature from Protobuf spec representation. + * + * @param table the FeatureTable to associate the constructed feature with. + * @param spec the Protobuf spec to contruct the Feature from. + * @return constructed Feature from the given Protobuf spec. + */ + public static FeatureV2 fromProto(FeatureTable table, FeatureSpecV2 spec) { + String labelsJSON = TypeConversion.convertMapToJsonString(spec.getLabelsMap()); + return new FeatureV2(table, spec.getName(), spec.getValueType(), labelsJSON); + } + + /** Convert this Feature to its Protobuf representation. */ + public FeatureSpecV2 toProto() { + Map labels = TypeConversion.convertJsonStringToMap(getLabelsJSON()); + return FeatureSpecV2.newBuilder() + .setName(getName()) + .setValueType(getType()) + .putAllLabels(labels) + .build(); + } + + /** + * Update the Feature from the given Protobuf representation. + * + * @param spec the Protobuf spec to update the Feature from. + * @throws IllegalArgumentException if the update will make prohibited changes. + */ + public void updateFromProto(FeatureSpecV2 spec) { + // Check for prohibited changes made in spec + if (!getName().equals(spec.getName())) { + throw new IllegalArgumentException( + String.format( + "Updating the name of a registered Feature is not allowed: %s to %s", + getName(), spec.getName())); + } + if (!getType().equals(spec.getValueType())) { + throw new IllegalArgumentException( + String.format( + "Updating the value type of a registered Feature is not allowed: %s to %s", + getType(), spec.getValueType())); + } + + // Update Feature based on spec + this.labelsJSON = TypeConversion.convertMapToJsonString(spec.getLabelsMap()); + } + + @Override + public int hashCode() { + return Objects.hash(getName(), getType(), getLabelsJSON()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + FeatureV2 feature = (FeatureV2) o; + return getName().equals(feature.getName()) + && getType().equals(feature.getType()) + && getLabelsJSON().equals(feature.getLabelsJSON()); + } +} diff --git a/core/src/main/java/feast/core/model/Project.java b/core/src/main/java/feast/core/model/Project.java index 8135289a009..e516f5868c4 100644 --- a/core/src/main/java/feast/core/model/Project.java +++ b/core/src/main/java/feast/core/model/Project.java @@ -59,6 +59,13 @@ public class Project { mappedBy = "project") private Set entities; + @OneToMany( + cascade = CascadeType.ALL, + fetch = FetchType.EAGER, + orphanRemoval = true, + mappedBy = "project") + private Set featureTables; + public Project() { super(); } @@ -67,6 +74,7 @@ public Project(String name) { this.name = name; this.featureSets = new HashSet<>(); this.entities = new HashSet<>(); + this.featureTables = new HashSet<>(); } public void addFeatureSet(FeatureSet featureSet) { diff --git a/core/src/main/java/feast/core/service/SpecService.java b/core/src/main/java/feast/core/service/SpecService.java index 287cc15394a..f570167c211 100644 --- a/core/src/main/java/feast/core/service/SpecService.java +++ b/core/src/main/java/feast/core/service/SpecService.java @@ -23,6 +23,7 @@ import com.google.protobuf.InvalidProtocolBufferException; import feast.core.dao.EntityRepository; import feast.core.dao.FeatureSetRepository; +import feast.core.dao.FeatureTableRepository; import feast.core.dao.ProjectRepository; import feast.core.dao.StoreRepository; import feast.core.exception.RegistrationException; @@ -30,17 +31,24 @@ import feast.core.model.*; import feast.core.validators.EntityValidator; import feast.core.validators.FeatureSetValidator; +import feast.core.validators.FeatureTableValidator; import feast.proto.core.CoreServiceProto.ApplyEntityResponse; import feast.proto.core.CoreServiceProto.ApplyFeatureSetResponse; import feast.proto.core.CoreServiceProto.ApplyFeatureSetResponse.Status; +import feast.proto.core.CoreServiceProto.ApplyFeatureTableRequest; +import feast.proto.core.CoreServiceProto.ApplyFeatureTableResponse; import feast.proto.core.CoreServiceProto.GetEntityRequest; import feast.proto.core.CoreServiceProto.GetEntityResponse; import feast.proto.core.CoreServiceProto.GetFeatureSetRequest; import feast.proto.core.CoreServiceProto.GetFeatureSetResponse; +import feast.proto.core.CoreServiceProto.GetFeatureTableRequest; +import feast.proto.core.CoreServiceProto.GetFeatureTableResponse; import feast.proto.core.CoreServiceProto.ListEntitiesRequest; import feast.proto.core.CoreServiceProto.ListEntitiesResponse; import feast.proto.core.CoreServiceProto.ListFeatureSetsRequest; import feast.proto.core.CoreServiceProto.ListFeatureSetsResponse; +import feast.proto.core.CoreServiceProto.ListFeatureTablesRequest; +import feast.proto.core.CoreServiceProto.ListFeatureTablesResponse; import feast.proto.core.CoreServiceProto.ListFeaturesRequest; import feast.proto.core.CoreServiceProto.ListFeaturesResponse; import feast.proto.core.CoreServiceProto.ListStoresRequest; @@ -53,12 +61,15 @@ import feast.proto.core.EntityProto; import feast.proto.core.FeatureSetProto; import feast.proto.core.FeatureSetProto.FeatureSetStatus; +import feast.proto.core.FeatureTableProto.FeatureTableSpec; 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 java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -75,6 +86,7 @@ public class SpecService { private final EntityRepository entityRepository; private final FeatureSetRepository featureSetRepository; + private final FeatureTableRepository tableRepository; private final ProjectRepository projectRepository; private final StoreRepository storeRepository; private final Source defaultSource; @@ -83,11 +95,13 @@ public class SpecService { public SpecService( EntityRepository entityRepository, FeatureSetRepository featureSetRepository, + FeatureTableRepository tableRepository, StoreRepository storeRepository, ProjectRepository projectRepository, Source defaultSource) { this.entityRepository = entityRepository; this.featureSetRepository = featureSetRepository; + this.tableRepository = tableRepository; this.storeRepository = storeRepository; this.projectRepository = projectRepository; this.defaultSource = defaultSource; @@ -297,6 +311,7 @@ public ListFeaturesResponse listFeatures(ListFeaturesRequest.Filter filter) { // Currently defaults to all FeatureSets List featureSets = featureSetRepository.findAllByNameLikeAndProject_NameOrderByNameAsc("%", project); + // TODO: List features in Feature Tables. ListFeaturesResponse.Builder response = ListFeaturesResponse.newBuilder(); if (entities.size() > 0) { @@ -575,18 +590,12 @@ public ApplyFeatureSetResponse applyFeatureSet(FeatureSetProto.FeatureSet newFea } /** - * Sets project to 'default' if project is not specified in feature set + * Resolves the project name by returning name if given, autofilling default project otherwise. * - * @param featureSet Feature set which needs to be imputed with default project. + * @param projectName name of the project to resolve. */ - public FeatureSetProto.FeatureSet imputeProjectName(FeatureSetProto.FeatureSet featureSet) { - if (featureSet.getSpec().getProject().isEmpty()) { - return featureSet - .toBuilder() - .setSpec(featureSet.getSpec().toBuilder().setProject(Project.DEFAULT_NAME).build()) - .build(); - } - return featureSet; + public static String resolveProjectName(String projectName) { + return (projectName.isEmpty()) ? Project.DEFAULT_NAME : projectName; } /** @@ -625,4 +634,111 @@ public UpdateStoreResponse updateStore(UpdateStoreRequest updateStoreRequest) .setStore(updateStoreRequest.getStore()) .build(); } + + /** + * Applies the given FeatureTable to the FeatureTable registry. Creates the FeatureTable if does + * not exist, otherwise updates the existing FeatureTable. Applies FeatureTable in project if + * specified, otherwise in default project. + * + * @param request Contains FeatureTable spec and project parameters used to create or update a + * FeatureTable. + * @throws NoSuchElementException projects and entities referenced in request do not exist. + * @return response containing the applied FeatureTable spec. + */ + @Transactional + public ApplyFeatureTableResponse applyFeatureTable(ApplyFeatureTableRequest request) { + String projectName = resolveProjectName(request.getProject()); + + // Check that specification provided is valid + FeatureTableSpec applySpec = request.getTableSpec(); + FeatureTableValidator.validateSpec(applySpec); + + // Prevent apply if the project is archived. + Project project = projectRepository.findById(projectName).orElse(new Project(projectName)); + if (project.isArchived()) { + throw new IllegalArgumentException( + String.format( + "Cannot apply Feature Table to archived Project: (table: %s, project: %s)", + applySpec.getName(), projectName)); + } + + // Create or update depending on whether there is an existing Feature Table + Optional existingTable = + tableRepository.findFeatureTableByNameAndProject_Name(applySpec.getName(), projectName); + FeatureTable table = FeatureTable.fromProto(projectName, applySpec, entityRepository); + if (existingTable.isPresent() && table.equals(existingTable.get())) { + // Skip update if no change is detected + return ApplyFeatureTableResponse.newBuilder().setTable(existingTable.get().toProto()).build(); + } + if (existingTable.isPresent()) { + existingTable.get().updateFromProto(applySpec); + table = existingTable.get(); + } + + // Commit FeatureTable to database and return applied FeatureTable + tableRepository.saveAndFlush(table); + return ApplyFeatureTableResponse.newBuilder().setTable(table.toProto()).build(); + } + + /** + * List the FeatureTables matching the filter in the given filter. Scopes down listing to project + * if specified, the default project otherwise. + * + * @param filter Filter containing the desired project and labels + * @return ListFeatureTablesResponse with list of FeatureTables found matching the filter + */ + @Transactional + public ListFeatureTablesResponse listFeatureTables(ListFeatureTablesRequest.Filter filter) { + String projectName = resolveProjectName(filter.getProject()); + Map labelsFilter = filter.getLabelsMap(); + + checkValidCharacters(projectName, "project"); + + List matchingTables = tableRepository.findAllByProject_Name(projectName); + + ListFeatureTablesResponse.Builder response = ListFeatureTablesResponse.newBuilder(); + + if (matchingTables.size() > 0) { + matchingTables = + matchingTables.stream() + .filter(table -> table.hasAllLabels(labelsFilter)) + .collect(Collectors.toList()); + } + for (FeatureTable table : matchingTables) { + response.addTables(table.toProto()); + } + + return response.build(); + } + + /** + * Get the FeatureTable with the name and project specified in the request. Gets FeatureTable in + * project if specified, otherwise in default project. + * + * @param request containing the retrieval parameters. + * @throws NoSuchElementException if no FeatureTable matches given request. + * @return response containing the requested FeatureTable. + */ + @Transactional + public GetFeatureTableResponse getFeatureTable(GetFeatureTableRequest request) { + String projectName = resolveProjectName(request.getProject()); + String featureTableName = request.getName(); + + checkValidCharacters(projectName, "project"); + checkValidCharacters(featureTableName, "featureTable"); + + Optional retrieveTable = + tableRepository.findFeatureTableByNameAndProject_Name(featureTableName, projectName); + if (retrieveTable.isEmpty()) { + throw new NoSuchElementException( + String.format( + "No such Feature Table: (project: %s, name: %s)", projectName, featureTableName)); + } + + // Build GetFeatureTableResponse + GetFeatureTableResponse response = + GetFeatureTableResponse.newBuilder().setTable(retrieveTable.get().toProto()).build(); + + return response; + } } diff --git a/core/src/main/java/feast/core/validators/FeatureTableValidator.java b/core/src/main/java/feast/core/validators/FeatureTableValidator.java new file mode 100644 index 00000000000..d50be2736cd --- /dev/null +++ b/core/src/main/java/feast/core/validators/FeatureTableValidator.java @@ -0,0 +1,81 @@ +/* + * 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.validators; + +import static feast.core.validators.Matchers.*; + +import feast.proto.core.FeatureProto.FeatureSpecV2; +import feast.proto.core.FeatureTableProto.FeatureTableSpec; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; + +public class FeatureTableValidator { + protected static final Set RESERVED_NAMES = + Set.of("created_timestamp", "event_timestamp"); + + public static void validateSpec(FeatureTableSpec spec) { + if (spec.getName().isEmpty()) { + throw new IllegalArgumentException("FeatureTable name must be provided"); + } + if (spec.getLabelsMap().containsKey("")) { + throw new IllegalArgumentException("FeatureTable cannot have labels with empty key."); + } + if (spec.getEntitiesCount() == 0) { + throw new IllegalArgumentException("FeatureTable entities list cannot be empty."); + } + if (spec.getFeaturesCount() == 0) { + throw new IllegalArgumentException("FeatureTable features list cannot be empty."); + } + if (!spec.hasBatchSource()) { + throw new IllegalArgumentException("FeatureTable batch source cannot be empty."); + } + + checkValidCharacters(spec.getName(), "FeatureTable"); + spec.getFeaturesList().forEach(FeatureTableValidator::validateFeatureSpec); + + // Check that BigQuery reference defined for BigQuery source is valid + if (!spec.getBatchSource().getBigqueryOptions().getTableRef().isEmpty()) { + checkValidBigQueryTableRef( + spec.getBatchSource().getBigqueryOptions().getTableRef(), "FeatureTable"); + } + + // Check that features and entities defined in FeatureTable do not use reserved names + ArrayList fieldNames = new ArrayList<>(spec.getEntitiesList()); + fieldNames.addAll( + spec.getFeaturesList().stream().map(FeatureSpecV2::getName).collect(Collectors.toList())); + if (!Collections.disjoint(fieldNames, RESERVED_NAMES)) { + throw new IllegalArgumentException( + String.format( + "Reserved names has been used as Feature(s) names. Reserved: %s", RESERVED_NAMES)); + } + + // Check that Feature and Entity names in FeatureTable do not collide with each other + if (hasDuplicates(fieldNames)) { + throw new IllegalArgumentException( + String.format("Entity and Feature names within a Feature Table should be unique.")); + } + } + + private static void validateFeatureSpec(FeatureSpecV2 spec) { + checkValidCharacters(spec.getName(), "Feature"); + if (spec.getLabelsMap().containsKey("")) { + throw new IllegalArgumentException("Features cannot have labels with empty key."); + } + } +} diff --git a/core/src/main/java/feast/core/validators/Matchers.java b/core/src/main/java/feast/core/validators/Matchers.java index ba12a83a79c..cd79eb06d7e 100644 --- a/core/src/main/java/feast/core/validators/Matchers.java +++ b/core/src/main/java/feast/core/validators/Matchers.java @@ -16,10 +16,14 @@ */ package feast.core.validators; +import java.util.Collection; +import java.util.HashSet; import java.util.regex.Pattern; public class Matchers { + private static Pattern BIGQUERY_TABLE_REF_REGEX = + Pattern.compile("[a-zA-Z0-9-]+[:]+[a-zA-Z0-9]+[.]+[a-zA-Z0-9_]*"); private static Pattern UPPER_SNAKE_CASE_REGEX = Pattern.compile("^[A-Z0-9]+(_[A-Z0-9]+)*$"); private static Pattern LOWER_SNAKE_CASE_REGEX = Pattern.compile("^[a-z0-9]+(_[a-z0-9]+)*$"); private static Pattern VALID_CHARACTERS_REGEX = Pattern.compile("^[a-zA-Z_][a-zA-Z0-9_]*$"); @@ -75,4 +79,20 @@ public static void checkValidCharactersAllowAsterisk(String input, String resour "argument must only contain alphanumeric characters, dashes, underscores, or an asterisk.")); } } + + public static void checkValidBigQueryTableRef(String input, String resource) + throws IllegalArgumentException { + if (!BIGQUERY_TABLE_REF_REGEX.matcher(input).matches()) { + throw new IllegalArgumentException( + String.format( + ERROR_MESSAGE_TEMPLATE, + resource, + input, + "argument must be in the form of .")); + } + } + + public static boolean hasDuplicates(Collection strings) { + return (new HashSet<>(strings)).size() < strings.size(); + } } diff --git a/core/src/main/resources/db/migration/V2.8__Feature_Tables_API.sql b/core/src/main/resources/db/migration/V2.8__Feature_Tables_API.sql new file mode 100644 index 00000000000..a083dfd1ae6 --- /dev/null +++ b/core/src/main/resources/db/migration/V2.8__Feature_Tables_API.sql @@ -0,0 +1,66 @@ +-- Data Sources SQL table used to Store Feature project +CREATE TABLE data_sources ( + id bigint NOT NULL, + type character varying(255) NOT NULL, + field_mapping text NOT NULL, + timestamp_column character varying(255), + date_partition_column character varying(255), + -- Only the options corresponding to type should set & non-null + -- DataSource Options + config text, + + CONSTRAINT data_sources_pkey PRIMARY KEY (id) +); + +-- Feature Table SQL table used to store FeatureTables protobuf +CREATE TABLE feature_tables ( + id bigint NOT NULL, + project_name character varying(255), + name character varying(255) NOT NULL, + created timestamp without time zone NOT NULL, + last_updated timestamp without time zone NOT NULL, + labels text NOT NULL, + max_age bigint NOT NULL, + stream_source_id bigint, + batch_source_id bigint, + revision int NOT NULL , + + CONSTRAINT feature_tables_pkey PRIMARY KEY (id), + CONSTRAINT feature_tables_project_fkey FOREIGN KEY (project_name) + REFERENCES projects(name), + CONSTRAINT feature_tables_stream_data_source_fkey FOREIGN KEY (stream_source_id) + REFERENCES data_sources(id), + CONSTRAINT feature_tables_batch_data_source_fkey FOREIGN KEY (batch_source_id) + REFERENCES data_sources(id), + -- Feature Tables must be unique within a project + CONSTRAINT feature_tables_unique_project UNIQUE (name, project_name) +); + +-- Join table between feature tables and entities V2 +CREATE TABLE feature_tables_entities_v2 ( + feature_table_id bigint NOT NULL, + entity_v2_id bigint NOT NULL, + + CONSTRAINT feature_tables_entities_v2_pkey PRIMARY KEY (feature_table_id, entity_v2_id), + CONSTRAINT feature_tables_entities_v2_join_feature_tables_fkey + FOREIGN KEY (feature_table_id) REFERENCES feature_tables(id), + CONSTRAINT feature_tables_entities_v2_join_entities_v2_fkey + FOREIGN KEY (entity_v2_id) REFERENCES entities_v2 (id) +); + + +-- Feature v2 SQL table used to store FeatureSpecV2 protobuf +CREATE TABLE features_v2 ( + id bigint NOT NULL, + feature_table_id bigint NOT NULL, + name character varying(255), + type character varying(255), + labels text NOT NULL, + + CONSTRAINT features_v2_pkey PRIMARY KEY (id), + CONSTRAINT features_v2_feature_table_fkey FOREIGN KEY (feature_table_id) + REFERENCES feature_tables(id), + -- Features should be unique within a feature set + CONSTRAINT feature_v2_feature_table_unique UNIQUE (name, feature_table_id) +); + diff --git a/core/src/test/java/feast/core/auth/CoreServiceAuthTest.java b/core/src/test/java/feast/core/auth/CoreServiceAuthTest.java index 24d313dcd3a..c6f88f3f630 100644 --- a/core/src/test/java/feast/core/auth/CoreServiceAuthTest.java +++ b/core/src/test/java/feast/core/auth/CoreServiceAuthTest.java @@ -100,9 +100,7 @@ public void shouldNotApplyFeatureSetIfNotProjectMember() throws InvalidProtocolB StreamRecorder responseObserver = StreamRecorder.create(); FeatureSetProto.FeatureSet incomingFeatureSet = newDummyFeatureSet("f2", 1, project).toProto(); - doReturn(incomingFeatureSet) - .when(specService) - .imputeProjectName(any(FeatureSetProto.FeatureSet.class)); + FeatureSetProto.FeatureSetSpec incomingFeatureSetSpec = incomingFeatureSet.getSpec().toBuilder().build(); FeatureSetProto.FeatureSet spec = diff --git a/core/src/test/java/feast/core/model/DataSourceTest.java b/core/src/test/java/feast/core/model/DataSourceTest.java new file mode 100644 index 00000000000..8e7400dc8b2 --- /dev/null +++ b/core/src/test/java/feast/core/model/DataSourceTest.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.core.model; + +import static feast.proto.core.DataSourceProto.DataSource.SourceType.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsEqual.equalTo; + +import feast.common.it.DataGenerator; +import feast.proto.core.DataSourceProto; +import feast.proto.core.DataSourceProto.DataSource.BigQueryOptions; +import feast.proto.core.DataSourceProto.DataSource.KinesisOptions; +import java.util.List; +import java.util.Map; +import org.junit.Test; + +public class DataSourceTest { + @Test + public void shouldSerializeFieldMappingAsJSON() { + Map expectedMap = Map.of("test", "value"); + + getTestSpecs() + .forEach( + spec -> { + DataSource source = + DataSource.fromProto(spec.toBuilder().putAllFieldMapping(expectedMap).build()); + Map actualMap = source.getFieldsMap(); + assertThat(actualMap, equalTo(actualMap)); + }); + } + + @Test + public void shouldFromProtoBeReversableWithToProto() { + getTestSpecs() + .forEach( + expectedSpec -> { + DataSourceProto.DataSource actualSpec = DataSource.fromProto(expectedSpec).toProto(); + assertThat(actualSpec, equalTo(expectedSpec)); + }); + } + + private List getTestSpecs() { + return List.of( + DataGenerator.createFileDataSourceSpec("file:///path/to/file", "parquet", "ts_col", ""), + DataGenerator.createKafkaDataSourceSpec("localhost:9092", "topic", "class.path", "ts_col"), + DataSourceProto.DataSource.newBuilder() + .setType(BATCH_BIGQUERY) + .setBigqueryOptions( + BigQueryOptions.newBuilder().setTableRef("project:dataset.table").build()) + .build(), + DataSourceProto.DataSource.newBuilder() + .setType(STREAM_KINESIS) + .setKinesisOptions( + KinesisOptions.newBuilder() + .setRegion("ap-nowhere1") + .setStreamName("stream") + .setClassPath("class.path") + .build()) + .build()); + } +} diff --git a/core/src/test/java/feast/core/service/SpecServiceIT.java b/core/src/test/java/feast/core/service/SpecServiceIT.java index 11e76a4c543..482fa65d834 100644 --- a/core/src/test/java/feast/core/service/SpecServiceIT.java +++ b/core/src/test/java/feast/core/service/SpecServiceIT.java @@ -26,6 +26,7 @@ import static org.hamcrest.core.IsEqual.equalTo; import static org.hamcrest.core.IsIterableContaining.hasItem; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertThrows; import avro.shaded.com.google.common.collect.ImmutableMap; @@ -33,7 +34,9 @@ import feast.common.it.BaseIT; import feast.common.it.DataGenerator; import feast.common.it.SimpleCoreClient; +import feast.common.util.TestUtil; import feast.proto.core.*; +import feast.proto.core.FeatureTableProto.FeatureTableSpec; import feast.proto.types.ValueProto; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; @@ -68,20 +71,38 @@ public static void globalSetUp(@Value("${grpc.server.port}") int port) { public void initState() { SourceProto.Source source = DataGenerator.getDefaultSource(); - apiClient.simpleApplyEntity( - "default", + EntityProto.EntitySpecV2 entitySpec1 = DataGenerator.createEntitySpecV2( "entity1", "Entity 1 description", ValueProto.ValueType.Enum.STRING, - ImmutableMap.of("label_key", "label_value"))); - apiClient.simpleApplyEntity( - "default", + ImmutableMap.of("label_key", "label_value")); + EntityProto.EntitySpecV2 entitySpec2 = DataGenerator.createEntitySpecV2( "entity2", "Entity 2 description", ValueProto.ValueType.Enum.STRING, - ImmutableMap.of("label_key2", "label_value2"))); + ImmutableMap.of("label_key2", "label_value2")); + apiClient.simpleApplyEntity("default", entitySpec1); + apiClient.simpleApplyEntity("default", entitySpec2); + apiClient.applyFeatureTable( + "default", + DataGenerator.createFeatureTableSpec( + "featuretable1", + Arrays.asList("entity1", "entity2"), + new HashMap<>() { + { + put("feature1", ValueProto.ValueType.Enum.STRING); + put("feature2", ValueProto.ValueType.Enum.FLOAT); + } + }, + 7200, + ImmutableMap.of("feat_key2", "feat_value2")) + .toBuilder() + .setBatchSource( + DataGenerator.createFileDataSourceSpec( + "file:///path/to/file", "parquet", "ts_col", "")) + .build()); apiClient.simpleApplyEntity( "project1", DataGenerator.createEntitySpecV2( @@ -265,6 +286,59 @@ public void shouldThrowExceptionGivenWildcardProject() { } } + @Nested + class ListFeatureTables { + @Test + public void shouldFilterFeatureTablesByProjectAndLabels() { + CoreServiceProto.ListFeatureTablesRequest.Filter filter = + CoreServiceProto.ListFeatureTablesRequest.Filter.newBuilder() + .setProject("default") + .putAllLabels(ImmutableMap.of("feat_key2", "feat_value2")) + .build(); + List featureTables = + apiClient.simpleListFeatureTables(filter); + + assertThat(featureTables, hasSize(1)); + assertThat( + featureTables, + hasItem(hasProperty("spec", hasProperty("name", equalTo("featuretable1"))))); + } + + @Test + public void shouldUseDefaultProjectIfProjectUnspecified() { + CoreServiceProto.ListFeatureTablesRequest.Filter filter = + CoreServiceProto.ListFeatureTablesRequest.Filter.newBuilder() + .setProject("default") + .build(); + List featureTables = + apiClient.simpleListFeatureTables(filter); + + assertThat(featureTables, hasSize(1)); + assertThat( + featureTables, + hasItem(hasProperty("spec", hasProperty("name", equalTo("featuretable1"))))); + } + + @Test + public void shouldThrowExceptionGivenWildcardProject() { + CoreServiceProto.ListFeatureTablesRequest.Filter filter = + CoreServiceProto.ListFeatureTablesRequest.Filter.newBuilder() + .setProject("default*") + .build(); + StatusRuntimeException exc = + assertThrows( + StatusRuntimeException.class, () -> apiClient.simpleListFeatureTables(filter)); + + assertThat( + exc.getMessage(), + equalTo( + String.format( + "INVALID_ARGUMENT: invalid value for project resource, %s: " + + "argument must only contain alphanumeric characters and underscores.", + filter.getProject()))); + } + } + @Nested class ApplyFeatureSet { @Test @@ -844,6 +918,51 @@ public void shouldRetrieveFromDefaultIfProjectNotSpecified() { } } + @Nested + class GetFeatureTable { + @Test + public void shouldThrowExceptionGivenNoSuchFeatureTable() { + String projectName = "default"; + String featureTableName = "invalid_table"; + StatusRuntimeException exc = + assertThrows( + StatusRuntimeException.class, + () -> apiClient.simpleGetFeatureTable(projectName, featureTableName)); + + assertThat( + exc.getMessage(), + equalTo( + String.format( + "NOT_FOUND: No such Feature Table: (project: %s, name: %s)", + projectName, featureTableName))); + } + + @Test + public void shouldReturnFeatureTableIfExists() { + FeatureTableSpec featureTableSpec = + DataGenerator.createFeatureTableSpec( + "featuretable1", + Arrays.asList("entity1", "entity2"), + new HashMap<>() { + { + put("feature1", ValueProto.ValueType.Enum.STRING); + put("feature2", ValueProto.ValueType.Enum.FLOAT); + } + }, + 7200, + ImmutableMap.of("feat_key2", "feat_value2")) + .toBuilder() + .setBatchSource( + DataGenerator.createFileDataSourceSpec( + "file:///path/to/file", "parquet", "ts_col", "")) + .build(); + FeatureTableProto.FeatureTable featureTable = + apiClient.simpleGetFeatureTable("default", "featuretable1"); + + assertTrue(TestUtil.compareFeatureTableSpec(featureTable.getSpec(), featureTableSpec)); + } + } + @Nested class ListStores { @Test @@ -933,4 +1052,306 @@ public void shouldFilterFeaturesByEntitiesAndLabels() { assertThat(result5, hasKey(equalTo("default/fs2:sum"))); } } + + @Nested + public class ApplyFeatureTable { + private FeatureTableSpec getTestSpec() { + return DataGenerator.createFeatureTableSpec( + "ft", + List.of("entity1", "entity2"), + Map.of( + "feature1", ValueProto.ValueType.Enum.INT64, + "feature2", ValueProto.ValueType.Enum.FLOAT), + 3600, + Map.of()) + .toBuilder() + .setBatchSource( + DataGenerator.createFileDataSourceSpec( + "file:///path/to/file", "parquet", "ts_col", "")) + .setStreamSource( + DataGenerator.createKafkaDataSourceSpec( + "localhost:9092", "topic", "class.path", "ts_col")) + .build(); + } + + @Test + public void shouldApplyNewValidTable() { + FeatureTableProto.FeatureTable table = apiClient.applyFeatureTable("default", getTestSpec()); + + assertTrue(TestUtil.compareFeatureTableSpec(table.getSpec(), getTestSpec())); + assertThat(table.getMeta().getRevision(), equalTo(0L)); + } + + @Test + public void shouldUpdateExistingTableWithValidSpec() { + FeatureTableProto.FeatureTable table = apiClient.applyFeatureTable("default", getTestSpec()); + + FeatureTableSpec updatedSpec = + DataGenerator.createFeatureTableSpec( + "ft", + List.of("entity1", "entity2"), + Map.of( + "feature2", ValueProto.ValueType.Enum.FLOAT, + "feature3", ValueProto.ValueType.Enum.INT64, + "feature4", ValueProto.ValueType.Enum.INT64), + 2100, + Map.of("test", "labels")) + .toBuilder() + .setStreamSource( + DataGenerator.createFileDataSourceSpec( + "file:///path/to/file", "parquet", "ts_col", "")) + .setBatchSource( + DataGenerator.createKafkaDataSourceSpec( + "localhost:9092", "topic", "class.path", "ts_col")) + .build(); + FeatureTableProto.FeatureTable updatedTable = + apiClient.applyFeatureTable("default", updatedSpec); + + assertTrue(TestUtil.compareFeatureTableSpec(updatedTable.getSpec(), updatedSpec)); + assertThat(updatedTable.getMeta().getRevision(), equalTo(table.getMeta().getRevision() + 1L)); + } + + @Test + public void shouldNotUpdateIfNoChanges() { + FeatureTableProto.FeatureTable table = apiClient.applyFeatureTable("default", getTestSpec()); + FeatureTableProto.FeatureTable updatedTable = + apiClient.applyFeatureTable("default", getTestSpec()); + + assertThat(updatedTable.getMeta().getRevision(), equalTo(table.getMeta().getRevision())); + } + + @Test + public void shouldErrorOnMissingBatchSource() { + FeatureTableProto.FeatureTableSpec spec = + DataGenerator.createFeatureTableSpec( + "ft", + List.of("entity1"), + Map.of("event_timestamp", ValueProto.ValueType.Enum.INT64), + 3600, + Map.of()) + .toBuilder() + .build(); + + StatusRuntimeException exc = + assertThrows( + StatusRuntimeException.class, () -> apiClient.applyFeatureTable("default", spec)); + + assertThat( + exc.getMessage(), + equalTo("INVALID_ARGUMENT: FeatureTable batch source cannot be empty.")); + } + + @Test + public void shouldErrorIfEntityChangeOnUpdate() { + List entities = Arrays.asList("entity1", "entity2"); + FeatureTableProto.FeatureTableSpec spec = + DataGenerator.createFeatureTableSpec( + "featuretable1", + Arrays.asList("entity1"), + new HashMap<>() { + { + put("feature1", ValueProto.ValueType.Enum.STRING); + put("feature2", ValueProto.ValueType.Enum.FLOAT); + } + }, + 7200, + ImmutableMap.of("feat_key2", "feat_value2")) + .toBuilder() + .setBatchSource( + DataGenerator.createFileDataSourceSpec( + "file:///path/to/file", "parquet", "ts_col", "")) + .build(); + + StatusRuntimeException exc = + assertThrows( + StatusRuntimeException.class, () -> apiClient.applyFeatureTable("default", spec)); + + assertThat( + exc.getMessage(), + equalTo( + String.format( + "INVALID_ARGUMENT: Updating the entities of a registered FeatureTable is not allowed: %s to %s", + entities, spec.getEntitiesList()))); + } + + @Test + public void shouldErrorIfFeatureValueTypeChangeOnUpdate() { + FeatureTableProto.FeatureTableSpec spec = + DataGenerator.createFeatureTableSpec( + "featuretable1", + Arrays.asList("entity1", "entity2"), + new HashMap<>() { + { + put("feature1", ValueProto.ValueType.Enum.STRING); + put("feature2", ValueProto.ValueType.Enum.STRING_LIST); + } + }, + 7200, + ImmutableMap.of("feat_key2", "feat_value2")) + .toBuilder() + .setBatchSource( + DataGenerator.createFileDataSourceSpec( + "file:///path/to/file", "parquet", "ts_col", "")) + .build(); + + StatusRuntimeException exc = + assertThrows( + StatusRuntimeException.class, () -> apiClient.applyFeatureTable("default", spec)); + + assertThat( + exc.getMessage(), + equalTo( + String.format( + "INVALID_ARGUMENT: Updating the value type of a registered Feature is not allowed: %s to %s", + ValueProto.ValueType.Enum.FLOAT, ValueProto.ValueType.Enum.STRING_LIST))); + } + + @Test + public void shouldErrorOnInvalidBigQueryTableRef() { + String invalidTableRef = "invalid.bq:path"; + FeatureTableProto.FeatureTableSpec spec = + DataGenerator.createFeatureTableSpec( + "ft", + List.of("entity1"), + Map.of("event_timestamp", ValueProto.ValueType.Enum.INT64), + 3600, + Map.of()) + .toBuilder() + .setBatchSource( + DataGenerator.createBigQueryDataSourceSpec(invalidTableRef, "ts_col", "")) + .build(); + + StatusRuntimeException exc = + assertThrows( + StatusRuntimeException.class, () -> apiClient.applyFeatureTable("default", spec)); + + assertThat( + exc.getMessage(), + equalTo( + String.format( + "INVALID_ARGUMENT: invalid value for FeatureTable resource, %s: argument must be in the form of .", + invalidTableRef))); + } + + @Test + public void shouldErrorOnReservedNames() { + // Reserved name used as feature name + assertThrows( + StatusRuntimeException.class, + () -> + apiClient.applyFeatureTable( + "default", + DataGenerator.createFeatureTableSpec( + "ft", + List.of("entity1"), + Map.of("event_timestamp", ValueProto.ValueType.Enum.INT64), + 3600, + Map.of()) + .toBuilder() + .setBatchSource( + DataGenerator.createFileDataSourceSpec( + "file:///path/to/file", "parquet", "ts_col", "")) + .build())); + + // Reserved name used in as entity name + assertThrows( + StatusRuntimeException.class, + () -> + apiClient.applyFeatureTable( + "default", + DataGenerator.createFeatureTableSpec( + "ft", + List.of("created_timestamp"), + Map.of("feature1", ValueProto.ValueType.Enum.INT64), + 3600, + Map.of()) + .toBuilder() + .setBatchSource( + DataGenerator.createFileDataSourceSpec( + "file:///path/to/file", "parquet", "ts_col", "")) + .build())); + } + + @Test + public void shouldErrorOnInvalidName() { + // Invalid feature table name + assertThrows( + StatusRuntimeException.class, + () -> + apiClient.applyFeatureTable( + "default", + DataGenerator.createFeatureTableSpec( + "f-t", + List.of("entity1"), + Map.of("feature1", ValueProto.ValueType.Enum.INT64), + 3600, + Map.of()) + .toBuilder() + .setBatchSource( + DataGenerator.createFileDataSourceSpec( + "file:///path/to/file", "parquet", "ts_col", "")) + .build())); + + // Invalid feature name + assertThrows( + StatusRuntimeException.class, + () -> + apiClient.applyFeatureTable( + "default", + DataGenerator.createFeatureTableSpec( + "ft", + List.of("entity1"), + Map.of("feature-1", ValueProto.ValueType.Enum.INT64), + 3600, + Map.of()) + .toBuilder() + .setBatchSource( + DataGenerator.createFileDataSourceSpec( + "file:///path/to/file", "parquet", "ts_col", "")) + .build())); + } + + @Test + public void shouldErrorOnNotFoundEntityName() { + assertThrows( + StatusRuntimeException.class, + () -> + apiClient.applyFeatureTable( + "default", + DataGenerator.createFeatureTableSpec( + "ft1", + List.of("entity_not_found"), + Map.of("feature1", ValueProto.ValueType.Enum.INT64), + 3600, + Map.of()) + .toBuilder() + .setBatchSource( + DataGenerator.createFileDataSourceSpec( + "file:///path/to/file", "parquet", "ts_col", "")) + .build())); + } + + @Test + public void shouldErrorOnArchivedProject() { + apiClient.createProject("archived"); + apiClient.archiveProject("archived"); + + assertThrows( + StatusRuntimeException.class, + () -> + apiClient.applyFeatureTable( + "archived", + DataGenerator.createFeatureTableSpec( + "ft1", + List.of("entity1", "entity2"), + Map.of("feature1", ValueProto.ValueType.Enum.INT64), + 3600, + Map.of()) + .toBuilder() + .setBatchSource( + DataGenerator.createFileDataSourceSpec( + "file:///path/to/file", "parquet", "ts_col", "")) + .build())); + } + } } diff --git a/core/src/test/java/feast/core/validators/FeatureTableValidatorTest.java b/core/src/test/java/feast/core/validators/FeatureTableValidatorTest.java new file mode 100644 index 00000000000..8410799dc68 --- /dev/null +++ b/core/src/test/java/feast/core/validators/FeatureTableValidatorTest.java @@ -0,0 +1,69 @@ +/* + * 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 static feast.proto.types.ValueProto.ValueType.Enum.*; + +import feast.common.it.DataGenerator; +import feast.proto.core.FeatureProto.FeatureSpecV2; +import feast.proto.core.FeatureTableProto.FeatureTableSpec; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.junit.Test; + +public class FeatureTableValidatorTest { + + @Test(expected = IllegalArgumentException.class) + public void shouldErrorIfLabelsHasEmptyKey() { + Map badLabels = Map.of("", "empty"); + FeatureTableSpec badSpec = getTestSpec().toBuilder().putAllLabels(badLabels).build(); + FeatureTableValidator.validateSpec(badSpec); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldErrorIfFeaturesLabelsHasEmptyKey() { + Map badLabels = Map.of("", "empty"); + + List badFeatureSpecs = + getTestSpec().getFeaturesList().stream() + .map(featureSpec -> featureSpec.toBuilder().putAllLabels(badLabels).build()) + .collect(Collectors.toList()); + FeatureTableSpec badSpec = getTestSpec().toBuilder().addAllFeatures(badFeatureSpecs).build(); + FeatureTableValidator.validateSpec(badSpec); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldErrorIfUsedReservedName() { + FeatureTableSpec badSpec = + getTestSpec().toBuilder().addAllEntities(FeatureTableValidator.RESERVED_NAMES).build(); + FeatureTableValidator.validateSpec(badSpec); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldErrorIfNamesUsedNotUnique() { + FeatureTableSpec badSpec = + DataGenerator.createFeatureTableSpec( + "driver", List.of("region"), Map.of("region", STRING), 3600, Map.of()); + FeatureTableValidator.validateSpec(badSpec); + } + + private FeatureTableSpec getTestSpec() { + return DataGenerator.createFeatureTableSpec( + "driver", List.of("driver_id"), Map.of("n_drivers", INT64), 3600, Map.of()); + } +} diff --git a/core/src/test/resources/application-it.properties b/core/src/test/resources/application-it.properties index 81aed45a7a9..7e71d8293fb 100644 --- a/core/src/test/resources/application-it.properties +++ b/core/src/test/resources/application-it.properties @@ -21,6 +21,6 @@ feast.security.authorization.enabled = false feast.jobs.enabled=false -spring.datasource.hikari.maximum-pool-size=50 +spring.datasource.hikari.maximum-pool-size=40 spring.main.allow-bean-definition-overriding=true diff --git a/go.mod b/go.mod index f640fc77534..137fea3e197 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect golang.org/x/net v0.0.0-20200822124328-c89045814202 golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9 // indirect - golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346 // indirect + golang.org/x/tools v0.0.0-20201001230009-b5b87423c93b // indirect google.golang.org/grpc v1.29.1 google.golang.org/protobuf v1.25.0 // indirect gopkg.in/russross/blackfriday.v2 v2.0.0 // indirect diff --git a/go.sum b/go.sum index afc554287de..c8b8ce2c300 100644 --- a/go.sum +++ b/go.sum @@ -484,6 +484,12 @@ golang.org/x/tools v0.0.0-20200923182640-463111b69878 h1:VUw1+Jf6KJPf82mbTQMia6H golang.org/x/tools v0.0.0-20200923182640-463111b69878/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346 h1:hzJjkvxUIF3bSt+v8N5tBQNx/605vszZJ+3XsIamzZo= golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20200928201943-a0ef9b62deab h1:CyH2SDm5ATQiX9gtbMYfvNNed97A9v+TJFnUX/fTaJY= +golang.org/x/tools v0.0.0-20200928201943-a0ef9b62deab/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20200929223013-bf155c11ec6f h1:7+Nz9MyPqt2qMCTvNiRy1G0zYfkB7UCa+ayT6uVvbyI= +golang.org/x/tools v0.0.0-20200929223013-bf155c11ec6f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201001230009-b5b87423c93b h1:07IVqnnzaip3TGyl/cy32V5YP3FguWG4BybYDTBNpm0= +golang.org/x/tools v0.0.0-20201001230009-b5b87423c93b/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= diff --git a/protos/feast/core/CoreService.proto b/protos/feast/core/CoreService.proto index a6ccd8ce598..a62dc56758f 100644 --- a/protos/feast/core/CoreService.proto +++ b/protos/feast/core/CoreService.proto @@ -25,6 +25,7 @@ import "google/protobuf/timestamp.proto"; import "tensorflow_metadata/proto/v0/statistics.proto"; import "feast/core/Entity.proto"; import "feast/core/FeatureSet.proto"; +import "feast/core/FeatureTable.proto"; import "feast/core/Store.proto"; import "feast/core/FeatureSetReference.proto"; import "feast/core/IngestionJob.proto"; @@ -113,6 +114,26 @@ service CoreService { // Internal API for Job Controller to update featureSet's status once responsible ingestion job is running rpc UpdateFeatureSetStatus (UpdateFeatureSetStatusRequest) returns (UpdateFeatureSetStatusResponse); + /* Feature Tables */ + // Create or update an existing feature table. + // This function is idempotent - it will not create a new feature table if the schema does not change. + // Schema changes will update the feature table if the changes are valid. + // All changes except the following are valid: + // - Changes to feature table name. + // - Changes to entities + // - Changes to feature name and type + rpc ApplyFeatureTable (ApplyFeatureTableRequest) returns (ApplyFeatureTableResponse); + + // List feature tables that match a given filter. + // Returns the references of the Feature Tables matching that filter. If none are found, + // an empty list will be returned. + // If no filter is provided in the request, the response will match all the feature + // tables currently stored in the registry. + rpc ListFeatureTables (ListFeatureTablesRequest) returns (ListFeatureTablesResponse); + + // Returns a specific feature table + rpc GetFeatureTable (GetFeatureTableRequest) returns (GetFeatureTableResponse); + } service JobControllerService { @@ -432,4 +453,50 @@ message UpdateFeatureSetStatusRequest { FeatureSetStatus status = 2; } -message UpdateFeatureSetStatusResponse {} \ No newline at end of file +message UpdateFeatureSetStatusResponse {} + +message ApplyFeatureTableRequest { + // Optional. Name of the Project to apply the Feature Table to. + // If unspecified, will apply FeatureTable to the default project. + string project = 1; + // Feature Table specification to apply + FeatureTableSpec table_spec = 2; +} + +message ApplyFeatureTableResponse { + FeatureTable table = 1; +} + +message GetFeatureTableRequest { + // Optional. Name of the Project to retrieve the Feature Table from. + // If unspecified, will apply FeatureTable to the default project. + string project = 1; + + // Name of the FeatureTable to retrieve. + string name = 2; +} + +message GetFeatureTableResponse { + // The Feature Table retrieved. + FeatureTable table = 1; +} + +message ListFeatureTablesRequest { + message Filter { + // Optional. Specifies the name of the project to list Feature Tables in. + // If unspecified would list Feature Tables in the default project. + string project = 1; + + // Optional. Feature Tables with all matching labels will be returned. + // If unspecified would list Feature Tables without filtering by labels. + map labels = 3; + } + + // Filter used when listing Feature Tables + Filter filter = 1; +} + +message ListFeatureTablesResponse { + // List of matching Feature Tables + repeated FeatureTable tables = 1; +} diff --git a/protos/feast/core/DataSource.proto b/protos/feast/core/DataSource.proto new file mode 100644 index 00000000000..d7f11044352 --- /dev/null +++ b/protos/feast/core/DataSource.proto @@ -0,0 +1,103 @@ +// +// 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 = "DataSourceProto"; +option java_package = "feast.proto.core"; + +// Defines a Data Source that can be used source Feature data +message DataSource { + // Type of Data Source. + enum SourceType { + INVALID = 0; + BATCH_FILE = 1; + BATCH_BIGQUERY = 2; + STREAM_KAFKA = 3; + STREAM_KINESIS = 4; + } + SourceType type = 1; + + // Defines mapping between fields in the sourced data + // and fields in parent FeatureTable. + map field_mapping = 2; + + // Must specify timestamp column name + string timestamp_column = 3; + + // (Optional) Specify partition column + // useful for file sources + string date_partition_column = 4; + + // Defines options for DataSource that sources features from a file + message FileOptions { + // File Format of the file containing the features + string file_format = 1; + + // Target URL of file to retrieve and source features from. + // s3://path/to/file for AWS S3 storage + // gs://path/to/file for GCP GCS storage + // file:///path/to/file for local storage + string file_url = 2; + } + + // Defines options for DataSource that sources features from a BigQuery Query + message BigQueryOptions { + // Full table reference in the form of [project:dataset.table] + string table_ref = 1; + } + + // Defines options for DataSource that sources features from Kafka messages. + // Each message should be a Protobuf that can be decoded with the generated + // Java Protobuf class at the given class path + message KafkaOptions { + // Comma separated list of Kafka bootstrap servers. Used for feature tables without a defined source host[:port]] + string bootstrap_servers = 1; + + // Kafka topic to collect feature data from. + string topic = 2; + + // Classpath to the generated Java Protobuf class that can be used to decode + // Feature data from the obtained Kafka message + string class_path = 3; + } + + // Defines options for DataSource that sources features from Kinesis records. + // Each record should be a Protobuf that can be decoded with the generated + // Java Protobuf class at the given class path + message KinesisOptions { + // AWS region of the Kinesis stream + string region = 1; + + // Name of the Kinesis stream to obtain feature data from. + string stream_name = 2; + + // Classpath to the generated Java Protobuf class that can be used to decode + // Feature data from the obtained Kinesis record + string class_path = 3; + } + + // DataSource options. + oneof options { + FileOptions file_options = 11; + BigQueryOptions bigquery_options = 12; + KafkaOptions kafka_options = 13; + KinesisOptions kinesis_options = 14; + } +} diff --git a/protos/feast/core/Entity.proto b/protos/feast/core/Entity.proto index 693100c64ab..ec7b2a07c96 100644 --- a/protos/feast/core/Entity.proto +++ b/protos/feast/core/Entity.proto @@ -15,6 +15,7 @@ // syntax = "proto3"; + package feast.core; option java_package = "feast.proto.core"; option java_outer_classname = "EntityProto"; @@ -47,4 +48,4 @@ message EntitySpecV2 { message EntityMeta { google.protobuf.Timestamp created_timestamp = 1; google.protobuf.Timestamp last_updated_timestamp = 2; -} \ No newline at end of file +} diff --git a/protos/feast/core/Feature.proto b/protos/feast/core/Feature.proto new file mode 100644 index 00000000000..ea0d340a008 --- /dev/null +++ b/protos/feast/core/Feature.proto @@ -0,0 +1,36 @@ +// +// 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 = "FeatureProto"; +option java_package = "feast.proto.core"; + +import "feast/types/Value.proto"; + +message FeatureSpecV2 { + // Name of the feature. Not updatable. + string name = 1; + + // Value type of the feature. Not updatable. + feast.types.ValueType.Enum value_type = 2; + + // Labels for user defined metadata on a feature + map labels = 3; +} diff --git a/protos/feast/core/FeatureTable.proto b/protos/feast/core/FeatureTable.proto new file mode 100644 index 00000000000..8ddd5fab2ef --- /dev/null +++ b/protos/feast/core/FeatureTable.proto @@ -0,0 +1,79 @@ +// +// 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 = "FeatureTableProto"; +option java_package = "feast.proto.core"; + +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "feast/core/DataSource.proto"; +import "feast/core/Feature.proto"; + +message FeatureTable { + // User-specified specifications of this feature table. + FeatureTableSpec spec = 1; + + // System-populated metadata for this feature table. + FeatureTableMeta meta = 2; +} + +message FeatureTableSpec { + // Name of the feature set. Must be unique. Not updated. + string name = 1; + + // List names of entities to associate with the Features defined in this + // Feature Table. Not updatable. + repeated string entities = 3; + + // List of features specifications for each feature defined with this feature table. + repeated FeatureSpecV2 features = 4; + + // User defined metadata + map labels = 5; + + // Features in this feature table can only be retrieved from online serving + // younger than max age. Age is measured as the duration of time between + // the feature's event timestamp and when the feature is retrieved + // Feature values outside max age will be returned as unset values and indicated to end user + google.protobuf.Duration max_age = 6; + + // Batch/Offline DataSource to source batch/offline feature data. + // Only batch DataSource can be specified + // (ie source type should start with 'BATCH_') + DataSource batch_source = 7; + + // Stream/Online DataSource to source stream/online feature data. + // Only stream DataSource can be specified + // (ie source type should start with 'STREAM_') + DataSource stream_source = 8; +} + +message FeatureTableMeta { + // Time where this Feature Table is created + google.protobuf.Timestamp created_timestamp = 1; + + // Time where this Feature Table is last updated + google.protobuf.Timestamp last_updated_timestamp = 2; + + // Auto incrementing revision no. of this Feature Table + int64 revision = 3; +} diff --git a/sdk/go/protos/feast/core/CoreService.pb.go b/sdk/go/protos/feast/core/CoreService.pb.go index 55a59f6d30e..53f82b9a6a4 100644 --- a/sdk/go/protos/feast/core/CoreService.pb.go +++ b/sdk/go/protos/feast/core/CoreService.pb.go @@ -1932,6 +1932,313 @@ func (*UpdateFeatureSetStatusResponse) Descriptor() ([]byte, []int) { return file_feast_core_CoreService_proto_rawDescGZIP(), []int{35} } +type ApplyFeatureTableRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Optional. Name of the Project to apply the Feature Table to. + // If unspecified, will apply FeatureTable to the default project. + Project string `protobuf:"bytes,1,opt,name=project,proto3" json:"project,omitempty"` + // Feature Table specification to apply + TableSpec *FeatureTableSpec `protobuf:"bytes,2,opt,name=table_spec,json=tableSpec,proto3" json:"table_spec,omitempty"` +} + +func (x *ApplyFeatureTableRequest) Reset() { + *x = ApplyFeatureTableRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ApplyFeatureTableRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ApplyFeatureTableRequest) ProtoMessage() {} + +func (x *ApplyFeatureTableRequest) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[36] + 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 ApplyFeatureTableRequest.ProtoReflect.Descriptor instead. +func (*ApplyFeatureTableRequest) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{36} +} + +func (x *ApplyFeatureTableRequest) GetProject() string { + if x != nil { + return x.Project + } + return "" +} + +func (x *ApplyFeatureTableRequest) GetTableSpec() *FeatureTableSpec { + if x != nil { + return x.TableSpec + } + return nil +} + +type ApplyFeatureTableResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Table *FeatureTable `protobuf:"bytes,1,opt,name=table,proto3" json:"table,omitempty"` +} + +func (x *ApplyFeatureTableResponse) Reset() { + *x = ApplyFeatureTableResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[37] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ApplyFeatureTableResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ApplyFeatureTableResponse) ProtoMessage() {} + +func (x *ApplyFeatureTableResponse) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[37] + 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 ApplyFeatureTableResponse.ProtoReflect.Descriptor instead. +func (*ApplyFeatureTableResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{37} +} + +func (x *ApplyFeatureTableResponse) GetTable() *FeatureTable { + if x != nil { + return x.Table + } + return nil +} + +type GetFeatureTableRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Optional. Name of the Project to retrieve the Feature Table from. + // If unspecified, will apply FeatureTable to the default project. + Project string `protobuf:"bytes,1,opt,name=project,proto3" json:"project,omitempty"` + // Name of the FeatureTable to retrieve. + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *GetFeatureTableRequest) Reset() { + *x = GetFeatureTableRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[38] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetFeatureTableRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetFeatureTableRequest) ProtoMessage() {} + +func (x *GetFeatureTableRequest) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[38] + 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 GetFeatureTableRequest.ProtoReflect.Descriptor instead. +func (*GetFeatureTableRequest) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{38} +} + +func (x *GetFeatureTableRequest) GetProject() string { + if x != nil { + return x.Project + } + return "" +} + +func (x *GetFeatureTableRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type GetFeatureTableResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The Feature Table retrieved. + Table *FeatureTable `protobuf:"bytes,1,opt,name=table,proto3" json:"table,omitempty"` +} + +func (x *GetFeatureTableResponse) Reset() { + *x = GetFeatureTableResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[39] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetFeatureTableResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetFeatureTableResponse) ProtoMessage() {} + +func (x *GetFeatureTableResponse) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[39] + 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 GetFeatureTableResponse.ProtoReflect.Descriptor instead. +func (*GetFeatureTableResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{39} +} + +func (x *GetFeatureTableResponse) GetTable() *FeatureTable { + if x != nil { + return x.Table + } + return nil +} + +type ListFeatureTablesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Filter used when listing Feature Tables + Filter *ListFeatureTablesRequest_Filter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` +} + +func (x *ListFeatureTablesRequest) Reset() { + *x = ListFeatureTablesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[40] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListFeatureTablesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListFeatureTablesRequest) ProtoMessage() {} + +func (x *ListFeatureTablesRequest) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[40] + 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 ListFeatureTablesRequest.ProtoReflect.Descriptor instead. +func (*ListFeatureTablesRequest) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{40} +} + +func (x *ListFeatureTablesRequest) GetFilter() *ListFeatureTablesRequest_Filter { + if x != nil { + return x.Filter + } + return nil +} + +type ListFeatureTablesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // List of matching Feature Tables + Tables []*FeatureTable `protobuf:"bytes,1,rep,name=tables,proto3" json:"tables,omitempty"` +} + +func (x *ListFeatureTablesResponse) Reset() { + *x = ListFeatureTablesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[41] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListFeatureTablesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListFeatureTablesResponse) ProtoMessage() {} + +func (x *ListFeatureTablesResponse) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[41] + 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 ListFeatureTablesResponse.ProtoReflect.Descriptor instead. +func (*ListFeatureTablesResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{41} +} + +func (x *ListFeatureTablesResponse) GetTables() []*FeatureTable { + if x != nil { + return x.Tables + } + return nil +} + type ListFeatureSetsRequest_Filter struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1964,7 +2271,7 @@ type ListFeatureSetsRequest_Filter struct { func (x *ListFeatureSetsRequest_Filter) Reset() { *x = ListFeatureSetsRequest_Filter{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[36] + mi := &file_feast_core_CoreService_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1977,7 +2284,7 @@ func (x *ListFeatureSetsRequest_Filter) String() string { func (*ListFeatureSetsRequest_Filter) ProtoMessage() {} func (x *ListFeatureSetsRequest_Filter) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[36] + mi := &file_feast_core_CoreService_proto_msgTypes[42] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2038,7 +2345,7 @@ type ListEntitiesRequest_Filter struct { func (x *ListEntitiesRequest_Filter) Reset() { *x = ListEntitiesRequest_Filter{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[38] + mi := &file_feast_core_CoreService_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2051,7 +2358,7 @@ func (x *ListEntitiesRequest_Filter) String() string { func (*ListEntitiesRequest_Filter) ProtoMessage() {} func (x *ListEntitiesRequest_Filter) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[38] + mi := &file_feast_core_CoreService_proto_msgTypes[44] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2101,7 +2408,7 @@ type ListFeaturesRequest_Filter struct { func (x *ListFeaturesRequest_Filter) Reset() { *x = ListFeaturesRequest_Filter{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[40] + mi := &file_feast_core_CoreService_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2114,7 +2421,7 @@ func (x *ListFeaturesRequest_Filter) String() string { func (*ListFeaturesRequest_Filter) ProtoMessage() {} func (x *ListFeaturesRequest_Filter) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[40] + mi := &file_feast_core_CoreService_proto_msgTypes[46] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2163,7 +2470,7 @@ type ListStoresRequest_Filter struct { func (x *ListStoresRequest_Filter) Reset() { *x = ListStoresRequest_Filter{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[43] + mi := &file_feast_core_CoreService_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2176,7 +2483,7 @@ func (x *ListStoresRequest_Filter) String() string { func (*ListStoresRequest_Filter) ProtoMessage() {} func (x *ListStoresRequest_Filter) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[43] + mi := &file_feast_core_CoreService_proto_msgTypes[49] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2215,7 +2522,7 @@ type ListIngestionJobsRequest_Filter struct { func (x *ListIngestionJobsRequest_Filter) Reset() { *x = ListIngestionJobsRequest_Filter{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[44] + mi := &file_feast_core_CoreService_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2228,7 +2535,7 @@ func (x *ListIngestionJobsRequest_Filter) String() string { func (*ListIngestionJobsRequest_Filter) ProtoMessage() {} func (x *ListIngestionJobsRequest_Filter) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[44] + mi := &file_feast_core_CoreService_proto_msgTypes[50] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2265,6 +2572,65 @@ func (x *ListIngestionJobsRequest_Filter) GetStoreName() string { return "" } +type ListFeatureTablesRequest_Filter struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Optional. Specifies the name of the project to list Feature Tables in. + // If unspecified would list Feature Tables in the default project. + Project string `protobuf:"bytes,1,opt,name=project,proto3" json:"project,omitempty"` + // Optional. Feature Tables with all matching labels will be returned. + // If unspecified would list Feature Tables without filtering by labels. + Labels map[string]string `protobuf:"bytes,3,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *ListFeatureTablesRequest_Filter) Reset() { + *x = ListFeatureTablesRequest_Filter{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[51] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListFeatureTablesRequest_Filter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListFeatureTablesRequest_Filter) ProtoMessage() {} + +func (x *ListFeatureTablesRequest_Filter) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[51] + 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 ListFeatureTablesRequest_Filter.ProtoReflect.Descriptor instead. +func (*ListFeatureTablesRequest_Filter) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{40, 0} +} + +func (x *ListFeatureTablesRequest_Filter) GetProject() string { + if x != nil { + return x.Project + } + return "" +} + +func (x *ListFeatureTablesRequest_Filter) GetLabels() map[string]string { + if x != nil { + return x.Labels + } + return nil +} + var File_feast_core_CoreService_proto protoreflect.FileDescriptor var file_feast_core_CoreService_proto_rawDesc = []byte{ @@ -2279,362 +2645,425 @@ var file_feast_core_CoreService_proto_rawDesc = []byte{ 0x74, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 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, + 0x1a, 0x1d, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x46, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 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, 0xea, 0x02, 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, 0x8c, 0x02, 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, 0x12, 0x4d, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x35, 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, 0x2e, 0x4c, 0x61, 0x62, + 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, + 0x12, 0x34, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 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, 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, 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, 0x40, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x45, 0x6e, + 0x74, 0x69, 0x74, 0x79, 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, 0x12, + 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x3f, 0x0a, 0x11, 0x47, 0x65, 0x74, + 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, + 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, + 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0x81, 0x02, 0x0a, 0x13, 0x4c, + 0x69, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x3e, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x69, 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, 0xa9, 0x01, 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, 0x4a, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, + 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 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, 0x22, 0x46, + 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, + 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x08, 0x65, 0x6e, + 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, 0x9d, 0x02, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x46, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3e, + 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, + 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x46, 0x65, 0x61, 0x74, 0x75, 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, 0xc5, + 0x01, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x4a, 0x0a, 0x06, 0x6c, 0x61, 0x62, + 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x66, 0x65, 0x61, 0x73, + 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, + 0x72, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, + 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, + 0x73, 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, 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, 0x22, 0xb8, 0x01, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x46, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x4a, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x2e, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x1a, 0x54, 0x0a, 0x0d, 0x46, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 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, 0x2d, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 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, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 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, 0x5c, 0x0a, 0x12, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x70, 0x65, 0x63, 0x56, 0x32, 0x52, + 0x04, 0x73, 0x70, 0x65, 0x63, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x22, + 0x41, 0x0a, 0x13, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, + 0x74, 0x79, 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, 0xea, 0x02, 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, 0x8c, 0x02, 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, 0x12, 0x4d, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x35, 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, 0x2e, 0x4c, 0x61, - 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, - 0x73, 0x12, 0x34, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 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, 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, 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, 0x40, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x45, - 0x6e, 0x74, 0x69, 0x74, 0x79, 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, - 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x3f, 0x0a, 0x11, 0x47, 0x65, - 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x2a, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x12, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6e, 0x74, - 0x69, 0x74, 0x79, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0x81, 0x02, 0x0a, 0x13, - 0x4c, 0x69, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x3e, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x69, 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, 0xa9, 0x01, 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, 0x4a, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, - 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, - 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x69, - 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, - 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, 0x22, - 0x46, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, - 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x66, 0x65, 0x61, 0x73, - 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x08, 0x65, - 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, 0x9d, 0x02, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, - 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x3e, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x26, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 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, - 0xc5, 0x01, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x4a, 0x0a, 0x06, 0x6c, 0x61, - 0x62, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x66, 0x65, 0x61, - 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x6c, 0x74, - 0x65, 0x72, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, - 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, - 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, - 0x65, 0x73, 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, 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, 0x22, 0xb8, 0x01, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, - 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x4a, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x1a, 0x54, 0x0a, 0x0d, - 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 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, - 0x2d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 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, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, - 0x38, 0x01, 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, + 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, 0x5c, 0x0a, 0x12, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x45, 0x6e, 0x74, 0x69, 0x74, - 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, - 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x70, 0x65, 0x63, 0x56, 0x32, - 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, - 0x22, 0x41, 0x0a, 0x13, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, - 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x06, 0x65, 0x6e, 0x74, - 0x69, 0x74, 0x79, 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, + 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, 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, + 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, + 0x22, 0xb1, 0x02, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, + 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x53, 0x65, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 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, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, + 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, + 0x05, 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, 0x07, 0x65, 0x6e, 0x64, 0x44, 0x61, 0x74, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, + 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x0c, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x12, + 0x23, 0x0a, 0x0d, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x52, 0x65, 0x66, + 0x72, 0x65, 0x73, 0x68, 0x22, 0x9b, 0x01, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7b, 0x0a, 0x1f, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, + 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, + 0x69, 0x63, 0x73, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 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, 0x44, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x46, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, + 0x4c, 0x69, 0x73, 0x74, 0x52, 0x1c, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x46, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x4c, 0x69, + 0x73, 0x74, 0x22, 0x94, 0x01, 0x0a, 0x1d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x3d, 0x0a, 0x09, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, + 0x65, 0x18, 0x01, 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, 0x09, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, + 0x6e, 0x63, 0x65, 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, 0x22, 0x20, 0x0a, 0x1e, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x71, 0x0a, 0x18, 0x41, + 0x70, 0x70, 0x6c, 0x79, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 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, 0x3b, 0x0a, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x53, + 0x70, 0x65, 0x63, 0x52, 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x70, 0x65, 0x63, 0x22, 0x4b, + 0x0a, 0x19, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x54, 0x61, + 0x62, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x74, + 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x66, 0x65, 0x61, + 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x54, + 0x61, 0x62, 0x6c, 0x65, 0x52, 0x05, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x46, 0x0a, 0x16, 0x47, + 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 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, 0x22, 0x49, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, + 0x0a, 0x05, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 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, 0x22, 0xb1, 0x02, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, - 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x65, 0x74, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x53, 0x65, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 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, 0x73, 0x74, 0x61, 0x72, 0x74, - 0x44, 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, - 0x18, 0x05, 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, 0x07, 0x65, 0x6e, 0x64, 0x44, 0x61, 0x74, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x69, - 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x06, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x0c, 0x69, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, - 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, - 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x52, 0x65, - 0x66, 0x72, 0x65, 0x73, 0x68, 0x22, 0x9b, 0x01, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7b, 0x0a, 0x1f, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, - 0x74, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x73, - 0x74, 0x69, 0x63, 0x73, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 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, 0x44, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, - 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, - 0x73, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x1c, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x46, 0x65, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x4c, - 0x69, 0x73, 0x74, 0x22, 0x94, 0x01, 0x0a, 0x1d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x65, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3d, 0x0a, 0x09, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, - 0x63, 0x65, 0x18, 0x01, 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, 0x09, 0x72, 0x65, 0x66, 0x65, 0x72, - 0x65, 0x6e, 0x63, 0x65, 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, 0x22, 0x20, 0x0a, 0x1e, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xbe, 0x0a, 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, 0x48, 0x0a, 0x09, 0x47, 0x65, - 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1c, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, - 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, - 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 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, + 0x72, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x05, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x90, + 0x02, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x54, 0x61, + 0x62, 0x6c, 0x65, 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, 0x46, 0x65, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x51, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, - 0x12, 0x1f, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 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, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x12, 0x27, 0x2e, 0x66, 0x65, + 0x74, 0x75, 0x72, 0x65, 0x54, 0x61, 0x62, 0x6c, 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, 0xae, 0x01, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 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, 0x4f, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x54, 0x61, + 0x62, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x6c, 0x74, + 0x65, 0x72, 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, 0x22, 0x4d, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x54, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, + 0x0a, 0x06, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, + 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x06, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, + 0x32, 0xde, 0x0c, 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, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, - 0x65, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, - 0x69, 0x73, 0x74, 0x69, 0x63, 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, + 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, + 0x0a, 0x09, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1c, 0x2e, 0x66, 0x65, + 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x66, 0x65, 0x61, 0x73, + 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, + 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, 0x51, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x73, 0x12, 0x1f, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, + 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 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, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x46, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x12, + 0x27, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, + 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 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, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x0b, 0x41, 0x70, 0x70, 0x6c, 0x79, - 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1e, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, - 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, - 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x45, - 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x1f, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, - 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, - 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, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x69, - 0x65, 0x73, 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, 0x6f, 0x0a, 0x16, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x29, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, - 0x6f, 0x72, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x53, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x2a, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xbf, 0x02, - 0x0a, 0x14, 0x4a, 0x6f, 0x62, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 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, + 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, 0x41, + 0x70, 0x70, 0x6c, 0x79, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1e, 0x2e, 0x66, 0x65, 0x61, + 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x45, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x66, 0x65, 0x61, + 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x45, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x0c, 0x4c, + 0x69, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x1f, 0x2e, 0x66, 0x65, + 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x6e, 0x74, + 0x69, 0x74, 0x69, 0x65, 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, 0x45, 0x6e, + 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 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, 0x6f, 0x0a, 0x16, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x53, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x29, 0x2e, 0x66, 0x65, 0x61, + 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x53, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x60, 0x0a, 0x11, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x24, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x66, + 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x46, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x24, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, + 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 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, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x22, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, + 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x66, + 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x32, 0xbf, 0x02, 0x0a, 0x14, 0x4a, 0x6f, 0x62, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x6c, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 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 ( @@ -2650,7 +3079,7 @@ func file_feast_core_CoreService_proto_rawDescGZIP() []byte { } var file_feast_core_CoreService_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_feast_core_CoreService_proto_msgTypes = make([]protoimpl.MessageInfo, 45) +var file_feast_core_CoreService_proto_msgTypes = make([]protoimpl.MessageInfo, 53) var file_feast_core_CoreService_proto_goTypes = []interface{}{ (ApplyFeatureSetResponse_Status)(0), // 0: feast.core.ApplyFeatureSetResponse.Status (UpdateStoreResponse_Status)(0), // 1: feast.core.UpdateStoreResponse.Status @@ -2690,99 +3119,121 @@ var file_feast_core_CoreService_proto_goTypes = []interface{}{ (*GetFeatureStatisticsResponse)(nil), // 35: feast.core.GetFeatureStatisticsResponse (*UpdateFeatureSetStatusRequest)(nil), // 36: feast.core.UpdateFeatureSetStatusRequest (*UpdateFeatureSetStatusResponse)(nil), // 37: feast.core.UpdateFeatureSetStatusResponse - (*ListFeatureSetsRequest_Filter)(nil), // 38: feast.core.ListFeatureSetsRequest.Filter - nil, // 39: feast.core.ListFeatureSetsRequest.Filter.LabelsEntry - (*ListEntitiesRequest_Filter)(nil), // 40: feast.core.ListEntitiesRequest.Filter - nil, // 41: feast.core.ListEntitiesRequest.Filter.LabelsEntry - (*ListFeaturesRequest_Filter)(nil), // 42: feast.core.ListFeaturesRequest.Filter - nil, // 43: feast.core.ListFeaturesRequest.Filter.LabelsEntry - nil, // 44: feast.core.ListFeaturesResponse.FeaturesEntry - (*ListStoresRequest_Filter)(nil), // 45: feast.core.ListStoresRequest.Filter - (*ListIngestionJobsRequest_Filter)(nil), // 46: feast.core.ListIngestionJobsRequest.Filter - (*FeatureSet)(nil), // 47: feast.core.FeatureSet - (*Entity)(nil), // 48: feast.core.Entity - (*Store)(nil), // 49: feast.core.Store - (*EntitySpecV2)(nil), // 50: feast.core.EntitySpecV2 - (*IngestionJob)(nil), // 51: feast.core.IngestionJob - (*timestamp.Timestamp)(nil), // 52: google.protobuf.Timestamp - (*v0.DatasetFeatureStatisticsList)(nil), // 53: tensorflow.metadata.v0.DatasetFeatureStatisticsList - (*FeatureSetReference)(nil), // 54: feast.core.FeatureSetReference - (FeatureSetStatus)(0), // 55: feast.core.FeatureSetStatus - (*FeatureSpec)(nil), // 56: feast.core.FeatureSpec + (*ApplyFeatureTableRequest)(nil), // 38: feast.core.ApplyFeatureTableRequest + (*ApplyFeatureTableResponse)(nil), // 39: feast.core.ApplyFeatureTableResponse + (*GetFeatureTableRequest)(nil), // 40: feast.core.GetFeatureTableRequest + (*GetFeatureTableResponse)(nil), // 41: feast.core.GetFeatureTableResponse + (*ListFeatureTablesRequest)(nil), // 42: feast.core.ListFeatureTablesRequest + (*ListFeatureTablesResponse)(nil), // 43: feast.core.ListFeatureTablesResponse + (*ListFeatureSetsRequest_Filter)(nil), // 44: feast.core.ListFeatureSetsRequest.Filter + nil, // 45: feast.core.ListFeatureSetsRequest.Filter.LabelsEntry + (*ListEntitiesRequest_Filter)(nil), // 46: feast.core.ListEntitiesRequest.Filter + nil, // 47: feast.core.ListEntitiesRequest.Filter.LabelsEntry + (*ListFeaturesRequest_Filter)(nil), // 48: feast.core.ListFeaturesRequest.Filter + nil, // 49: feast.core.ListFeaturesRequest.Filter.LabelsEntry + nil, // 50: feast.core.ListFeaturesResponse.FeaturesEntry + (*ListStoresRequest_Filter)(nil), // 51: feast.core.ListStoresRequest.Filter + (*ListIngestionJobsRequest_Filter)(nil), // 52: feast.core.ListIngestionJobsRequest.Filter + (*ListFeatureTablesRequest_Filter)(nil), // 53: feast.core.ListFeatureTablesRequest.Filter + nil, // 54: feast.core.ListFeatureTablesRequest.Filter.LabelsEntry + (*FeatureSet)(nil), // 55: feast.core.FeatureSet + (*Entity)(nil), // 56: feast.core.Entity + (*Store)(nil), // 57: feast.core.Store + (*EntitySpecV2)(nil), // 58: feast.core.EntitySpecV2 + (*IngestionJob)(nil), // 59: feast.core.IngestionJob + (*timestamp.Timestamp)(nil), // 60: google.protobuf.Timestamp + (*v0.DatasetFeatureStatisticsList)(nil), // 61: tensorflow.metadata.v0.DatasetFeatureStatisticsList + (*FeatureSetReference)(nil), // 62: feast.core.FeatureSetReference + (FeatureSetStatus)(0), // 63: feast.core.FeatureSetStatus + (*FeatureTableSpec)(nil), // 64: feast.core.FeatureTableSpec + (*FeatureTable)(nil), // 65: feast.core.FeatureTable + (*FeatureSpec)(nil), // 66: feast.core.FeatureSpec } var file_feast_core_CoreService_proto_depIdxs = []int32{ - 47, // 0: feast.core.GetFeatureSetResponse.feature_set:type_name -> feast.core.FeatureSet - 38, // 1: feast.core.ListFeatureSetsRequest.filter:type_name -> feast.core.ListFeatureSetsRequest.Filter - 47, // 2: feast.core.ListFeatureSetsResponse.feature_sets:type_name -> feast.core.FeatureSet - 48, // 3: feast.core.GetEntityResponse.entity:type_name -> feast.core.Entity - 40, // 4: feast.core.ListEntitiesRequest.filter:type_name -> feast.core.ListEntitiesRequest.Filter - 48, // 5: feast.core.ListEntitiesResponse.entities:type_name -> feast.core.Entity - 42, // 6: feast.core.ListFeaturesRequest.filter:type_name -> feast.core.ListFeaturesRequest.Filter - 44, // 7: feast.core.ListFeaturesResponse.features:type_name -> feast.core.ListFeaturesResponse.FeaturesEntry - 45, // 8: feast.core.ListStoresRequest.filter:type_name -> feast.core.ListStoresRequest.Filter - 49, // 9: feast.core.ListStoresResponse.store:type_name -> feast.core.Store - 50, // 10: feast.core.ApplyEntityRequest.spec:type_name -> feast.core.EntitySpecV2 - 48, // 11: feast.core.ApplyEntityResponse.entity:type_name -> feast.core.Entity - 47, // 12: feast.core.ApplyFeatureSetRequest.feature_set:type_name -> feast.core.FeatureSet - 47, // 13: feast.core.ApplyFeatureSetResponse.feature_set:type_name -> feast.core.FeatureSet + 55, // 0: feast.core.GetFeatureSetResponse.feature_set:type_name -> feast.core.FeatureSet + 44, // 1: feast.core.ListFeatureSetsRequest.filter:type_name -> feast.core.ListFeatureSetsRequest.Filter + 55, // 2: feast.core.ListFeatureSetsResponse.feature_sets:type_name -> feast.core.FeatureSet + 56, // 3: feast.core.GetEntityResponse.entity:type_name -> feast.core.Entity + 46, // 4: feast.core.ListEntitiesRequest.filter:type_name -> feast.core.ListEntitiesRequest.Filter + 56, // 5: feast.core.ListEntitiesResponse.entities:type_name -> feast.core.Entity + 48, // 6: feast.core.ListFeaturesRequest.filter:type_name -> feast.core.ListFeaturesRequest.Filter + 50, // 7: feast.core.ListFeaturesResponse.features:type_name -> feast.core.ListFeaturesResponse.FeaturesEntry + 51, // 8: feast.core.ListStoresRequest.filter:type_name -> feast.core.ListStoresRequest.Filter + 57, // 9: feast.core.ListStoresResponse.store:type_name -> feast.core.Store + 58, // 10: feast.core.ApplyEntityRequest.spec:type_name -> feast.core.EntitySpecV2 + 56, // 11: feast.core.ApplyEntityResponse.entity:type_name -> feast.core.Entity + 55, // 12: feast.core.ApplyFeatureSetRequest.feature_set:type_name -> feast.core.FeatureSet + 55, // 13: feast.core.ApplyFeatureSetResponse.feature_set:type_name -> feast.core.FeatureSet 0, // 14: feast.core.ApplyFeatureSetResponse.status:type_name -> feast.core.ApplyFeatureSetResponse.Status - 49, // 15: feast.core.UpdateStoreRequest.store:type_name -> feast.core.Store - 49, // 16: feast.core.UpdateStoreResponse.store:type_name -> feast.core.Store + 57, // 15: feast.core.UpdateStoreRequest.store:type_name -> feast.core.Store + 57, // 16: feast.core.UpdateStoreResponse.store:type_name -> feast.core.Store 1, // 17: feast.core.UpdateStoreResponse.status:type_name -> feast.core.UpdateStoreResponse.Status - 46, // 18: feast.core.ListIngestionJobsRequest.filter:type_name -> feast.core.ListIngestionJobsRequest.Filter - 51, // 19: feast.core.ListIngestionJobsResponse.jobs:type_name -> feast.core.IngestionJob - 52, // 20: feast.core.GetFeatureStatisticsRequest.start_date:type_name -> google.protobuf.Timestamp - 52, // 21: feast.core.GetFeatureStatisticsRequest.end_date:type_name -> google.protobuf.Timestamp - 53, // 22: feast.core.GetFeatureStatisticsResponse.dataset_feature_statistics_list:type_name -> tensorflow.metadata.v0.DatasetFeatureStatisticsList - 54, // 23: feast.core.UpdateFeatureSetStatusRequest.reference:type_name -> feast.core.FeatureSetReference - 55, // 24: feast.core.UpdateFeatureSetStatusRequest.status:type_name -> feast.core.FeatureSetStatus - 39, // 25: feast.core.ListFeatureSetsRequest.Filter.labels:type_name -> feast.core.ListFeatureSetsRequest.Filter.LabelsEntry - 55, // 26: feast.core.ListFeatureSetsRequest.Filter.status:type_name -> feast.core.FeatureSetStatus - 41, // 27: feast.core.ListEntitiesRequest.Filter.labels:type_name -> feast.core.ListEntitiesRequest.Filter.LabelsEntry - 43, // 28: feast.core.ListFeaturesRequest.Filter.labels:type_name -> feast.core.ListFeaturesRequest.Filter.LabelsEntry - 56, // 29: feast.core.ListFeaturesResponse.FeaturesEntry.value:type_name -> feast.core.FeatureSpec - 54, // 30: feast.core.ListIngestionJobsRequest.Filter.feature_set_reference:type_name -> feast.core.FeatureSetReference - 18, // 31: feast.core.CoreService.GetFeastCoreVersion:input_type -> feast.core.GetFeastCoreVersionRequest - 2, // 32: feast.core.CoreService.GetFeatureSet:input_type -> feast.core.GetFeatureSetRequest - 6, // 33: feast.core.CoreService.GetEntity:input_type -> feast.core.GetEntityRequest - 4, // 34: feast.core.CoreService.ListFeatureSets:input_type -> feast.core.ListFeatureSetsRequest - 10, // 35: feast.core.CoreService.ListFeatures:input_type -> feast.core.ListFeaturesRequest - 34, // 36: feast.core.CoreService.GetFeatureStatistics:input_type -> feast.core.GetFeatureStatisticsRequest - 12, // 37: feast.core.CoreService.ListStores:input_type -> feast.core.ListStoresRequest - 16, // 38: feast.core.CoreService.ApplyFeatureSet:input_type -> feast.core.ApplyFeatureSetRequest - 14, // 39: feast.core.CoreService.ApplyEntity:input_type -> feast.core.ApplyEntityRequest - 8, // 40: feast.core.CoreService.ListEntities:input_type -> feast.core.ListEntitiesRequest - 20, // 41: feast.core.CoreService.UpdateStore:input_type -> feast.core.UpdateStoreRequest - 22, // 42: feast.core.CoreService.CreateProject:input_type -> feast.core.CreateProjectRequest - 24, // 43: feast.core.CoreService.ArchiveProject:input_type -> feast.core.ArchiveProjectRequest - 26, // 44: feast.core.CoreService.ListProjects:input_type -> feast.core.ListProjectsRequest - 36, // 45: feast.core.CoreService.UpdateFeatureSetStatus:input_type -> feast.core.UpdateFeatureSetStatusRequest - 28, // 46: feast.core.JobControllerService.ListIngestionJobs:input_type -> feast.core.ListIngestionJobsRequest - 30, // 47: feast.core.JobControllerService.RestartIngestionJob:input_type -> feast.core.RestartIngestionJobRequest - 32, // 48: feast.core.JobControllerService.StopIngestionJob:input_type -> feast.core.StopIngestionJobRequest - 19, // 49: feast.core.CoreService.GetFeastCoreVersion:output_type -> feast.core.GetFeastCoreVersionResponse - 3, // 50: feast.core.CoreService.GetFeatureSet:output_type -> feast.core.GetFeatureSetResponse - 7, // 51: feast.core.CoreService.GetEntity:output_type -> feast.core.GetEntityResponse - 5, // 52: feast.core.CoreService.ListFeatureSets:output_type -> feast.core.ListFeatureSetsResponse - 11, // 53: feast.core.CoreService.ListFeatures:output_type -> feast.core.ListFeaturesResponse - 35, // 54: feast.core.CoreService.GetFeatureStatistics:output_type -> feast.core.GetFeatureStatisticsResponse - 13, // 55: feast.core.CoreService.ListStores:output_type -> feast.core.ListStoresResponse - 17, // 56: feast.core.CoreService.ApplyFeatureSet:output_type -> feast.core.ApplyFeatureSetResponse - 15, // 57: feast.core.CoreService.ApplyEntity:output_type -> feast.core.ApplyEntityResponse - 9, // 58: feast.core.CoreService.ListEntities:output_type -> feast.core.ListEntitiesResponse - 21, // 59: feast.core.CoreService.UpdateStore:output_type -> feast.core.UpdateStoreResponse - 23, // 60: feast.core.CoreService.CreateProject:output_type -> feast.core.CreateProjectResponse - 25, // 61: feast.core.CoreService.ArchiveProject:output_type -> feast.core.ArchiveProjectResponse - 27, // 62: feast.core.CoreService.ListProjects:output_type -> feast.core.ListProjectsResponse - 37, // 63: feast.core.CoreService.UpdateFeatureSetStatus:output_type -> feast.core.UpdateFeatureSetStatusResponse - 29, // 64: feast.core.JobControllerService.ListIngestionJobs:output_type -> feast.core.ListIngestionJobsResponse - 31, // 65: feast.core.JobControllerService.RestartIngestionJob:output_type -> feast.core.RestartIngestionJobResponse - 33, // 66: feast.core.JobControllerService.StopIngestionJob:output_type -> feast.core.StopIngestionJobResponse - 49, // [49:67] is the sub-list for method output_type - 31, // [31:49] is the sub-list for method input_type - 31, // [31:31] is the sub-list for extension type_name - 31, // [31:31] is the sub-list for extension extendee - 0, // [0:31] is the sub-list for field type_name + 52, // 18: feast.core.ListIngestionJobsRequest.filter:type_name -> feast.core.ListIngestionJobsRequest.Filter + 59, // 19: feast.core.ListIngestionJobsResponse.jobs:type_name -> feast.core.IngestionJob + 60, // 20: feast.core.GetFeatureStatisticsRequest.start_date:type_name -> google.protobuf.Timestamp + 60, // 21: feast.core.GetFeatureStatisticsRequest.end_date:type_name -> google.protobuf.Timestamp + 61, // 22: feast.core.GetFeatureStatisticsResponse.dataset_feature_statistics_list:type_name -> tensorflow.metadata.v0.DatasetFeatureStatisticsList + 62, // 23: feast.core.UpdateFeatureSetStatusRequest.reference:type_name -> feast.core.FeatureSetReference + 63, // 24: feast.core.UpdateFeatureSetStatusRequest.status:type_name -> feast.core.FeatureSetStatus + 64, // 25: feast.core.ApplyFeatureTableRequest.table_spec:type_name -> feast.core.FeatureTableSpec + 65, // 26: feast.core.ApplyFeatureTableResponse.table:type_name -> feast.core.FeatureTable + 65, // 27: feast.core.GetFeatureTableResponse.table:type_name -> feast.core.FeatureTable + 53, // 28: feast.core.ListFeatureTablesRequest.filter:type_name -> feast.core.ListFeatureTablesRequest.Filter + 65, // 29: feast.core.ListFeatureTablesResponse.tables:type_name -> feast.core.FeatureTable + 45, // 30: feast.core.ListFeatureSetsRequest.Filter.labels:type_name -> feast.core.ListFeatureSetsRequest.Filter.LabelsEntry + 63, // 31: feast.core.ListFeatureSetsRequest.Filter.status:type_name -> feast.core.FeatureSetStatus + 47, // 32: feast.core.ListEntitiesRequest.Filter.labels:type_name -> feast.core.ListEntitiesRequest.Filter.LabelsEntry + 49, // 33: feast.core.ListFeaturesRequest.Filter.labels:type_name -> feast.core.ListFeaturesRequest.Filter.LabelsEntry + 66, // 34: feast.core.ListFeaturesResponse.FeaturesEntry.value:type_name -> feast.core.FeatureSpec + 62, // 35: feast.core.ListIngestionJobsRequest.Filter.feature_set_reference:type_name -> feast.core.FeatureSetReference + 54, // 36: feast.core.ListFeatureTablesRequest.Filter.labels:type_name -> feast.core.ListFeatureTablesRequest.Filter.LabelsEntry + 18, // 37: feast.core.CoreService.GetFeastCoreVersion:input_type -> feast.core.GetFeastCoreVersionRequest + 2, // 38: feast.core.CoreService.GetFeatureSet:input_type -> feast.core.GetFeatureSetRequest + 6, // 39: feast.core.CoreService.GetEntity:input_type -> feast.core.GetEntityRequest + 4, // 40: feast.core.CoreService.ListFeatureSets:input_type -> feast.core.ListFeatureSetsRequest + 10, // 41: feast.core.CoreService.ListFeatures:input_type -> feast.core.ListFeaturesRequest + 34, // 42: feast.core.CoreService.GetFeatureStatistics:input_type -> feast.core.GetFeatureStatisticsRequest + 12, // 43: feast.core.CoreService.ListStores:input_type -> feast.core.ListStoresRequest + 16, // 44: feast.core.CoreService.ApplyFeatureSet:input_type -> feast.core.ApplyFeatureSetRequest + 14, // 45: feast.core.CoreService.ApplyEntity:input_type -> feast.core.ApplyEntityRequest + 8, // 46: feast.core.CoreService.ListEntities:input_type -> feast.core.ListEntitiesRequest + 20, // 47: feast.core.CoreService.UpdateStore:input_type -> feast.core.UpdateStoreRequest + 22, // 48: feast.core.CoreService.CreateProject:input_type -> feast.core.CreateProjectRequest + 24, // 49: feast.core.CoreService.ArchiveProject:input_type -> feast.core.ArchiveProjectRequest + 26, // 50: feast.core.CoreService.ListProjects:input_type -> feast.core.ListProjectsRequest + 36, // 51: feast.core.CoreService.UpdateFeatureSetStatus:input_type -> feast.core.UpdateFeatureSetStatusRequest + 38, // 52: feast.core.CoreService.ApplyFeatureTable:input_type -> feast.core.ApplyFeatureTableRequest + 42, // 53: feast.core.CoreService.ListFeatureTables:input_type -> feast.core.ListFeatureTablesRequest + 40, // 54: feast.core.CoreService.GetFeatureTable:input_type -> feast.core.GetFeatureTableRequest + 28, // 55: feast.core.JobControllerService.ListIngestionJobs:input_type -> feast.core.ListIngestionJobsRequest + 30, // 56: feast.core.JobControllerService.RestartIngestionJob:input_type -> feast.core.RestartIngestionJobRequest + 32, // 57: feast.core.JobControllerService.StopIngestionJob:input_type -> feast.core.StopIngestionJobRequest + 19, // 58: feast.core.CoreService.GetFeastCoreVersion:output_type -> feast.core.GetFeastCoreVersionResponse + 3, // 59: feast.core.CoreService.GetFeatureSet:output_type -> feast.core.GetFeatureSetResponse + 7, // 60: feast.core.CoreService.GetEntity:output_type -> feast.core.GetEntityResponse + 5, // 61: feast.core.CoreService.ListFeatureSets:output_type -> feast.core.ListFeatureSetsResponse + 11, // 62: feast.core.CoreService.ListFeatures:output_type -> feast.core.ListFeaturesResponse + 35, // 63: feast.core.CoreService.GetFeatureStatistics:output_type -> feast.core.GetFeatureStatisticsResponse + 13, // 64: feast.core.CoreService.ListStores:output_type -> feast.core.ListStoresResponse + 17, // 65: feast.core.CoreService.ApplyFeatureSet:output_type -> feast.core.ApplyFeatureSetResponse + 15, // 66: feast.core.CoreService.ApplyEntity:output_type -> feast.core.ApplyEntityResponse + 9, // 67: feast.core.CoreService.ListEntities:output_type -> feast.core.ListEntitiesResponse + 21, // 68: feast.core.CoreService.UpdateStore:output_type -> feast.core.UpdateStoreResponse + 23, // 69: feast.core.CoreService.CreateProject:output_type -> feast.core.CreateProjectResponse + 25, // 70: feast.core.CoreService.ArchiveProject:output_type -> feast.core.ArchiveProjectResponse + 27, // 71: feast.core.CoreService.ListProjects:output_type -> feast.core.ListProjectsResponse + 37, // 72: feast.core.CoreService.UpdateFeatureSetStatus:output_type -> feast.core.UpdateFeatureSetStatusResponse + 39, // 73: feast.core.CoreService.ApplyFeatureTable:output_type -> feast.core.ApplyFeatureTableResponse + 43, // 74: feast.core.CoreService.ListFeatureTables:output_type -> feast.core.ListFeatureTablesResponse + 41, // 75: feast.core.CoreService.GetFeatureTable:output_type -> feast.core.GetFeatureTableResponse + 29, // 76: feast.core.JobControllerService.ListIngestionJobs:output_type -> feast.core.ListIngestionJobsResponse + 31, // 77: feast.core.JobControllerService.RestartIngestionJob:output_type -> feast.core.RestartIngestionJobResponse + 33, // 78: feast.core.JobControllerService.StopIngestionJob:output_type -> feast.core.StopIngestionJobResponse + 58, // [58:79] is the sub-list for method output_type + 37, // [37:58] is the sub-list for method input_type + 37, // [37:37] is the sub-list for extension type_name + 37, // [37:37] is the sub-list for extension extendee + 0, // [0:37] is the sub-list for field type_name } func init() { file_feast_core_CoreService_proto_init() } @@ -2792,6 +3243,7 @@ func file_feast_core_CoreService_proto_init() { } file_feast_core_Entity_proto_init() file_feast_core_FeatureSet_proto_init() + file_feast_core_FeatureTable_proto_init() file_feast_core_Store_proto_init() file_feast_core_FeatureSetReference_proto_init() file_feast_core_IngestionJob_proto_init() @@ -3229,7 +3681,19 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListFeatureSetsRequest_Filter); i { + switch v := v.(*ApplyFeatureTableRequest); 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[37].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ApplyFeatureTableResponse); i { case 0: return &v.state case 1: @@ -3241,7 +3705,19 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListEntitiesRequest_Filter); i { + switch v := v.(*GetFeatureTableRequest); 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[39].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetFeatureTableResponse); i { case 0: return &v.state case 1: @@ -3253,6 +3729,54 @@ func file_feast_core_CoreService_proto_init() { } } file_feast_core_CoreService_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListFeatureTablesRequest); 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[41].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListFeatureTablesResponse); 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[42].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[44].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListEntitiesRequest_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[46].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ListFeaturesRequest_Filter); i { case 0: return &v.state @@ -3264,7 +3788,7 @@ func file_feast_core_CoreService_proto_init() { return nil } } - file_feast_core_CoreService_proto_msgTypes[43].Exporter = func(v interface{}, i int) interface{} { + file_feast_core_CoreService_proto_msgTypes[49].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ListStoresRequest_Filter); i { case 0: return &v.state @@ -3276,7 +3800,7 @@ func file_feast_core_CoreService_proto_init() { return nil } } - file_feast_core_CoreService_proto_msgTypes[44].Exporter = func(v interface{}, i int) interface{} { + file_feast_core_CoreService_proto_msgTypes[50].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ListIngestionJobsRequest_Filter); i { case 0: return &v.state @@ -3288,6 +3812,18 @@ func file_feast_core_CoreService_proto_init() { return nil } } + file_feast_core_CoreService_proto_msgTypes[51].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListFeatureTablesRequest_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{ @@ -3295,7 +3831,7 @@ func file_feast_core_CoreService_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_feast_core_CoreService_proto_rawDesc, NumEnums: 2, - NumMessages: 45, + NumMessages: 53, NumExtensions: 0, NumServices: 2, }, @@ -3390,6 +3926,22 @@ type CoreServiceClient interface { ListProjects(ctx context.Context, in *ListProjectsRequest, opts ...grpc.CallOption) (*ListProjectsResponse, error) // Internal API for Job Controller to update featureSet's status once responsible ingestion job is running UpdateFeatureSetStatus(ctx context.Context, in *UpdateFeatureSetStatusRequest, opts ...grpc.CallOption) (*UpdateFeatureSetStatusResponse, error) + // Create or update an existing feature table. + // This function is idempotent - it will not create a new feature table if the schema does not change. + // Schema changes will update the feature table if the changes are valid. + // All changes except the following are valid: + // - Changes to feature table name. + // - Changes to entities + // - Changes to feature name and type + ApplyFeatureTable(ctx context.Context, in *ApplyFeatureTableRequest, opts ...grpc.CallOption) (*ApplyFeatureTableResponse, error) + // List feature tables that match a given filter. + // Returns the references of the Feature Tables matching that filter. If none are found, + // an empty list will be returned. + // If no filter is provided in the request, the response will match all the feature + // tables currently stored in the registry. + ListFeatureTables(ctx context.Context, in *ListFeatureTablesRequest, opts ...grpc.CallOption) (*ListFeatureTablesResponse, error) + // Returns a specific feature table + GetFeatureTable(ctx context.Context, in *GetFeatureTableRequest, opts ...grpc.CallOption) (*GetFeatureTableResponse, error) } type coreServiceClient struct { @@ -3535,6 +4087,33 @@ func (c *coreServiceClient) UpdateFeatureSetStatus(ctx context.Context, in *Upda return out, nil } +func (c *coreServiceClient) ApplyFeatureTable(ctx context.Context, in *ApplyFeatureTableRequest, opts ...grpc.CallOption) (*ApplyFeatureTableResponse, error) { + out := new(ApplyFeatureTableResponse) + err := c.cc.Invoke(ctx, "/feast.core.CoreService/ApplyFeatureTable", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *coreServiceClient) ListFeatureTables(ctx context.Context, in *ListFeatureTablesRequest, opts ...grpc.CallOption) (*ListFeatureTablesResponse, error) { + out := new(ListFeatureTablesResponse) + err := c.cc.Invoke(ctx, "/feast.core.CoreService/ListFeatureTables", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *coreServiceClient) GetFeatureTable(ctx context.Context, in *GetFeatureTableRequest, opts ...grpc.CallOption) (*GetFeatureTableResponse, error) { + out := new(GetFeatureTableResponse) + err := c.cc.Invoke(ctx, "/feast.core.CoreService/GetFeatureTable", 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 @@ -3605,6 +4184,22 @@ type CoreServiceServer interface { ListProjects(context.Context, *ListProjectsRequest) (*ListProjectsResponse, error) // Internal API for Job Controller to update featureSet's status once responsible ingestion job is running UpdateFeatureSetStatus(context.Context, *UpdateFeatureSetStatusRequest) (*UpdateFeatureSetStatusResponse, error) + // Create or update an existing feature table. + // This function is idempotent - it will not create a new feature table if the schema does not change. + // Schema changes will update the feature table if the changes are valid. + // All changes except the following are valid: + // - Changes to feature table name. + // - Changes to entities + // - Changes to feature name and type + ApplyFeatureTable(context.Context, *ApplyFeatureTableRequest) (*ApplyFeatureTableResponse, error) + // List feature tables that match a given filter. + // Returns the references of the Feature Tables matching that filter. If none are found, + // an empty list will be returned. + // If no filter is provided in the request, the response will match all the feature + // tables currently stored in the registry. + ListFeatureTables(context.Context, *ListFeatureTablesRequest) (*ListFeatureTablesResponse, error) + // Returns a specific feature table + GetFeatureTable(context.Context, *GetFeatureTableRequest) (*GetFeatureTableResponse, error) } // UnimplementedCoreServiceServer can be embedded to have forward compatible implementations. @@ -3656,6 +4251,15 @@ func (*UnimplementedCoreServiceServer) ListProjects(context.Context, *ListProjec func (*UnimplementedCoreServiceServer) UpdateFeatureSetStatus(context.Context, *UpdateFeatureSetStatusRequest) (*UpdateFeatureSetStatusResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateFeatureSetStatus not implemented") } +func (*UnimplementedCoreServiceServer) ApplyFeatureTable(context.Context, *ApplyFeatureTableRequest) (*ApplyFeatureTableResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ApplyFeatureTable not implemented") +} +func (*UnimplementedCoreServiceServer) ListFeatureTables(context.Context, *ListFeatureTablesRequest) (*ListFeatureTablesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListFeatureTables not implemented") +} +func (*UnimplementedCoreServiceServer) GetFeatureTable(context.Context, *GetFeatureTableRequest) (*GetFeatureTableResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetFeatureTable not implemented") +} func RegisterCoreServiceServer(s *grpc.Server, srv CoreServiceServer) { s.RegisterService(&_CoreService_serviceDesc, srv) @@ -3931,6 +4535,60 @@ func _CoreService_UpdateFeatureSetStatus_Handler(srv interface{}, ctx context.Co return interceptor(ctx, in, info, handler) } +func _CoreService_ApplyFeatureTable_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ApplyFeatureTableRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CoreServiceServer).ApplyFeatureTable(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/feast.core.CoreService/ApplyFeatureTable", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CoreServiceServer).ApplyFeatureTable(ctx, req.(*ApplyFeatureTableRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CoreService_ListFeatureTables_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListFeatureTablesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CoreServiceServer).ListFeatureTables(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/feast.core.CoreService/ListFeatureTables", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CoreServiceServer).ListFeatureTables(ctx, req.(*ListFeatureTablesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CoreService_GetFeatureTable_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetFeatureTableRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CoreServiceServer).GetFeatureTable(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/feast.core.CoreService/GetFeatureTable", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CoreServiceServer).GetFeatureTable(ctx, req.(*GetFeatureTableRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _CoreService_serviceDesc = grpc.ServiceDesc{ ServiceName: "feast.core.CoreService", HandlerType: (*CoreServiceServer)(nil), @@ -3995,6 +4653,18 @@ var _CoreService_serviceDesc = grpc.ServiceDesc{ MethodName: "UpdateFeatureSetStatus", Handler: _CoreService_UpdateFeatureSetStatus_Handler, }, + { + MethodName: "ApplyFeatureTable", + Handler: _CoreService_ApplyFeatureTable_Handler, + }, + { + MethodName: "ListFeatureTables", + Handler: _CoreService_ListFeatureTables_Handler, + }, + { + MethodName: "GetFeatureTable", + Handler: _CoreService_GetFeatureTable_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "feast/core/CoreService.proto", diff --git a/sdk/go/protos/feast/core/DataSource.pb.go b/sdk/go/protos/feast/core/DataSource.pb.go new file mode 100644 index 00000000000..8a21f3c3551 --- /dev/null +++ b/sdk/go/protos/feast/core/DataSource.pb.go @@ -0,0 +1,709 @@ +// +// 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.25.0 +// protoc v3.12.4 +// source: feast/core/DataSource.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 of Data Source. +type DataSource_SourceType int32 + +const ( + DataSource_INVALID DataSource_SourceType = 0 + DataSource_BATCH_FILE DataSource_SourceType = 1 + DataSource_BATCH_BIGQUERY DataSource_SourceType = 2 + DataSource_STREAM_KAFKA DataSource_SourceType = 3 + DataSource_STREAM_KINESIS DataSource_SourceType = 4 +) + +// Enum value maps for DataSource_SourceType. +var ( + DataSource_SourceType_name = map[int32]string{ + 0: "INVALID", + 1: "BATCH_FILE", + 2: "BATCH_BIGQUERY", + 3: "STREAM_KAFKA", + 4: "STREAM_KINESIS", + } + DataSource_SourceType_value = map[string]int32{ + "INVALID": 0, + "BATCH_FILE": 1, + "BATCH_BIGQUERY": 2, + "STREAM_KAFKA": 3, + "STREAM_KINESIS": 4, + } +) + +func (x DataSource_SourceType) Enum() *DataSource_SourceType { + p := new(DataSource_SourceType) + *p = x + return p +} + +func (x DataSource_SourceType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (DataSource_SourceType) Descriptor() protoreflect.EnumDescriptor { + return file_feast_core_DataSource_proto_enumTypes[0].Descriptor() +} + +func (DataSource_SourceType) Type() protoreflect.EnumType { + return &file_feast_core_DataSource_proto_enumTypes[0] +} + +func (x DataSource_SourceType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use DataSource_SourceType.Descriptor instead. +func (DataSource_SourceType) EnumDescriptor() ([]byte, []int) { + return file_feast_core_DataSource_proto_rawDescGZIP(), []int{0, 0} +} + +// Defines a Data Source that can be used source Feature data +type DataSource struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type DataSource_SourceType `protobuf:"varint,1,opt,name=type,proto3,enum=feast.core.DataSource_SourceType" json:"type,omitempty"` + // Defines mapping between fields in the sourced data + // and fields in parent FeatureTable. + FieldMapping map[string]string `protobuf:"bytes,2,rep,name=field_mapping,json=fieldMapping,proto3" json:"field_mapping,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // Must specify timestamp column name + TimestampColumn string `protobuf:"bytes,3,opt,name=timestamp_column,json=timestampColumn,proto3" json:"timestamp_column,omitempty"` + // (Optional) Specify partition column + // useful for file sources + DatePartitionColumn string `protobuf:"bytes,4,opt,name=date_partition_column,json=datePartitionColumn,proto3" json:"date_partition_column,omitempty"` + // DataSource options. + // + // Types that are assignable to Options: + // *DataSource_FileOptions_ + // *DataSource_BigqueryOptions + // *DataSource_KafkaOptions_ + // *DataSource_KinesisOptions_ + Options isDataSource_Options `protobuf_oneof:"options"` +} + +func (x *DataSource) Reset() { + *x = DataSource{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_DataSource_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DataSource) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DataSource) ProtoMessage() {} + +func (x *DataSource) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_DataSource_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 DataSource.ProtoReflect.Descriptor instead. +func (*DataSource) Descriptor() ([]byte, []int) { + return file_feast_core_DataSource_proto_rawDescGZIP(), []int{0} +} + +func (x *DataSource) GetType() DataSource_SourceType { + if x != nil { + return x.Type + } + return DataSource_INVALID +} + +func (x *DataSource) GetFieldMapping() map[string]string { + if x != nil { + return x.FieldMapping + } + return nil +} + +func (x *DataSource) GetTimestampColumn() string { + if x != nil { + return x.TimestampColumn + } + return "" +} + +func (x *DataSource) GetDatePartitionColumn() string { + if x != nil { + return x.DatePartitionColumn + } + return "" +} + +func (m *DataSource) GetOptions() isDataSource_Options { + if m != nil { + return m.Options + } + return nil +} + +func (x *DataSource) GetFileOptions() *DataSource_FileOptions { + if x, ok := x.GetOptions().(*DataSource_FileOptions_); ok { + return x.FileOptions + } + return nil +} + +func (x *DataSource) GetBigqueryOptions() *DataSource_BigQueryOptions { + if x, ok := x.GetOptions().(*DataSource_BigqueryOptions); ok { + return x.BigqueryOptions + } + return nil +} + +func (x *DataSource) GetKafkaOptions() *DataSource_KafkaOptions { + if x, ok := x.GetOptions().(*DataSource_KafkaOptions_); ok { + return x.KafkaOptions + } + return nil +} + +func (x *DataSource) GetKinesisOptions() *DataSource_KinesisOptions { + if x, ok := x.GetOptions().(*DataSource_KinesisOptions_); ok { + return x.KinesisOptions + } + return nil +} + +type isDataSource_Options interface { + isDataSource_Options() +} + +type DataSource_FileOptions_ struct { + FileOptions *DataSource_FileOptions `protobuf:"bytes,11,opt,name=file_options,json=fileOptions,proto3,oneof"` +} + +type DataSource_BigqueryOptions struct { + BigqueryOptions *DataSource_BigQueryOptions `protobuf:"bytes,12,opt,name=bigquery_options,json=bigqueryOptions,proto3,oneof"` +} + +type DataSource_KafkaOptions_ struct { + KafkaOptions *DataSource_KafkaOptions `protobuf:"bytes,13,opt,name=kafka_options,json=kafkaOptions,proto3,oneof"` +} + +type DataSource_KinesisOptions_ struct { + KinesisOptions *DataSource_KinesisOptions `protobuf:"bytes,14,opt,name=kinesis_options,json=kinesisOptions,proto3,oneof"` +} + +func (*DataSource_FileOptions_) isDataSource_Options() {} + +func (*DataSource_BigqueryOptions) isDataSource_Options() {} + +func (*DataSource_KafkaOptions_) isDataSource_Options() {} + +func (*DataSource_KinesisOptions_) isDataSource_Options() {} + +// Defines options for DataSource that sources features from a file +type DataSource_FileOptions struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // File Format of the file containing the features + FileFormat string `protobuf:"bytes,1,opt,name=file_format,json=fileFormat,proto3" json:"file_format,omitempty"` + // Target URL of file to retrieve and source features from. + // s3://path/to/file for AWS S3 storage + // gs://path/to/file for GCP GCS storage + // file:///path/to/file for local storage + FileUrl string `protobuf:"bytes,2,opt,name=file_url,json=fileUrl,proto3" json:"file_url,omitempty"` +} + +func (x *DataSource_FileOptions) Reset() { + *x = DataSource_FileOptions{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_DataSource_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DataSource_FileOptions) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DataSource_FileOptions) ProtoMessage() {} + +func (x *DataSource_FileOptions) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_DataSource_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 DataSource_FileOptions.ProtoReflect.Descriptor instead. +func (*DataSource_FileOptions) Descriptor() ([]byte, []int) { + return file_feast_core_DataSource_proto_rawDescGZIP(), []int{0, 1} +} + +func (x *DataSource_FileOptions) GetFileFormat() string { + if x != nil { + return x.FileFormat + } + return "" +} + +func (x *DataSource_FileOptions) GetFileUrl() string { + if x != nil { + return x.FileUrl + } + return "" +} + +// Defines options for DataSource that sources features from a BigQuery Query +type DataSource_BigQueryOptions struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Full table reference in the form of [project:dataset.table] + TableRef string `protobuf:"bytes,1,opt,name=table_ref,json=tableRef,proto3" json:"table_ref,omitempty"` +} + +func (x *DataSource_BigQueryOptions) Reset() { + *x = DataSource_BigQueryOptions{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_DataSource_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DataSource_BigQueryOptions) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DataSource_BigQueryOptions) ProtoMessage() {} + +func (x *DataSource_BigQueryOptions) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_DataSource_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 DataSource_BigQueryOptions.ProtoReflect.Descriptor instead. +func (*DataSource_BigQueryOptions) Descriptor() ([]byte, []int) { + return file_feast_core_DataSource_proto_rawDescGZIP(), []int{0, 2} +} + +func (x *DataSource_BigQueryOptions) GetTableRef() string { + if x != nil { + return x.TableRef + } + return "" +} + +// Defines options for DataSource that sources features from Kafka messages. +// Each message should be a Protobuf that can be decoded with the generated +// Java Protobuf class at the given class path +type DataSource_KafkaOptions struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Comma separated list of Kafka bootstrap servers. Used for feature tables without a defined source host[:port]] + BootstrapServers string `protobuf:"bytes,1,opt,name=bootstrap_servers,json=bootstrapServers,proto3" json:"bootstrap_servers,omitempty"` + // Kafka topic to collect feature data from. + Topic string `protobuf:"bytes,2,opt,name=topic,proto3" json:"topic,omitempty"` + // Classpath to the generated Java Protobuf class that can be used to decode + // Feature data from the obtained Kafka message + ClassPath string `protobuf:"bytes,3,opt,name=class_path,json=classPath,proto3" json:"class_path,omitempty"` +} + +func (x *DataSource_KafkaOptions) Reset() { + *x = DataSource_KafkaOptions{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_DataSource_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DataSource_KafkaOptions) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DataSource_KafkaOptions) ProtoMessage() {} + +func (x *DataSource_KafkaOptions) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_DataSource_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 DataSource_KafkaOptions.ProtoReflect.Descriptor instead. +func (*DataSource_KafkaOptions) Descriptor() ([]byte, []int) { + return file_feast_core_DataSource_proto_rawDescGZIP(), []int{0, 3} +} + +func (x *DataSource_KafkaOptions) GetBootstrapServers() string { + if x != nil { + return x.BootstrapServers + } + return "" +} + +func (x *DataSource_KafkaOptions) GetTopic() string { + if x != nil { + return x.Topic + } + return "" +} + +func (x *DataSource_KafkaOptions) GetClassPath() string { + if x != nil { + return x.ClassPath + } + return "" +} + +// Defines options for DataSource that sources features from Kinesis records. +// Each record should be a Protobuf that can be decoded with the generated +// Java Protobuf class at the given class path +type DataSource_KinesisOptions struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // AWS region of the Kinesis stream + Region string `protobuf:"bytes,1,opt,name=region,proto3" json:"region,omitempty"` + // Name of the Kinesis stream to obtain feature data from. + StreamName string `protobuf:"bytes,2,opt,name=stream_name,json=streamName,proto3" json:"stream_name,omitempty"` + // Classpath to the generated Java Protobuf class that can be used to decode + // Feature data from the obtained Kinesis record + ClassPath string `protobuf:"bytes,3,opt,name=class_path,json=classPath,proto3" json:"class_path,omitempty"` +} + +func (x *DataSource_KinesisOptions) Reset() { + *x = DataSource_KinesisOptions{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_DataSource_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DataSource_KinesisOptions) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DataSource_KinesisOptions) ProtoMessage() {} + +func (x *DataSource_KinesisOptions) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_DataSource_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 DataSource_KinesisOptions.ProtoReflect.Descriptor instead. +func (*DataSource_KinesisOptions) Descriptor() ([]byte, []int) { + return file_feast_core_DataSource_proto_rawDescGZIP(), []int{0, 4} +} + +func (x *DataSource_KinesisOptions) GetRegion() string { + if x != nil { + return x.Region + } + return "" +} + +func (x *DataSource_KinesisOptions) GetStreamName() string { + if x != nil { + return x.StreamName + } + return "" +} + +func (x *DataSource_KinesisOptions) GetClassPath() string { + if x != nil { + return x.ClassPath + } + return "" +} + +var File_feast_core_DataSource_proto protoreflect.FileDescriptor + +var file_feast_core_DataSource_proto_rawDesc = []byte{ + 0x0a, 0x1b, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x44, 0x61, 0x74, + 0x61, 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, 0xb5, 0x08, 0x0a, 0x0a, 0x44, 0x61, + 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x53, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, + 0x4d, 0x0a, 0x0d, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x0c, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x29, + 0x0a, 0x10, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x5f, 0x63, 0x6f, 0x6c, 0x75, + 0x6d, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x12, 0x32, 0x0a, 0x15, 0x64, 0x61, 0x74, + 0x65, 0x5f, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6c, 0x75, + 0x6d, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, + 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x12, 0x47, 0x0a, + 0x0c, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, + 0x2e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x46, 0x69, 0x6c, 0x65, + 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x48, 0x00, 0x52, 0x0b, 0x66, 0x69, 0x6c, 0x65, 0x4f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x53, 0x0a, 0x10, 0x62, 0x69, 0x67, 0x71, 0x75, 0x65, + 0x72, 0x79, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x26, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x44, 0x61, + 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x42, 0x69, 0x67, 0x51, 0x75, 0x65, 0x72, + 0x79, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x48, 0x00, 0x52, 0x0f, 0x62, 0x69, 0x67, 0x71, + 0x75, 0x65, 0x72, 0x79, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x4a, 0x0a, 0x0d, 0x6b, + 0x61, 0x66, 0x6b, 0x61, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0d, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, + 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4b, 0x61, 0x66, 0x6b, 0x61, + 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x48, 0x00, 0x52, 0x0c, 0x6b, 0x61, 0x66, 0x6b, 0x61, + 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x50, 0x0a, 0x0f, 0x6b, 0x69, 0x6e, 0x65, 0x73, + 0x69, 0x73, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x25, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x44, 0x61, + 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4b, 0x69, 0x6e, 0x65, 0x73, 0x69, 0x73, + 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x48, 0x00, 0x52, 0x0e, 0x6b, 0x69, 0x6e, 0x65, 0x73, + 0x69, 0x73, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x3f, 0x0a, 0x11, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 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, 0x1a, 0x49, 0x0a, 0x0b, 0x46, 0x69, + 0x6c, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x69, 0x6c, + 0x65, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x66, 0x69, 0x6c, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x69, + 0x6c, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x69, + 0x6c, 0x65, 0x55, 0x72, 0x6c, 0x1a, 0x2e, 0x0a, 0x0f, 0x42, 0x69, 0x67, 0x51, 0x75, 0x65, 0x72, + 0x79, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x61, 0x62, 0x6c, + 0x65, 0x5f, 0x72, 0x65, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x61, 0x62, + 0x6c, 0x65, 0x52, 0x65, 0x66, 0x1a, 0x70, 0x0a, 0x0c, 0x4b, 0x61, 0x66, 0x6b, 0x61, 0x4f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 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, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x61, 0x73, + 0x73, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6c, + 0x61, 0x73, 0x73, 0x50, 0x61, 0x74, 0x68, 0x1a, 0x68, 0x0a, 0x0e, 0x4b, 0x69, 0x6e, 0x65, 0x73, + 0x69, 0x73, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, + 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, + 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x5f, 0x70, 0x61, 0x74, 0x68, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x50, 0x61, 0x74, + 0x68, 0x22, 0x63, 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, 0x0e, 0x0a, 0x0a, + 0x42, 0x41, 0x54, 0x43, 0x48, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, + 0x42, 0x41, 0x54, 0x43, 0x48, 0x5f, 0x42, 0x49, 0x47, 0x51, 0x55, 0x45, 0x52, 0x59, 0x10, 0x02, + 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4d, 0x5f, 0x4b, 0x41, 0x46, 0x4b, 0x41, + 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4d, 0x5f, 0x4b, 0x49, 0x4e, + 0x45, 0x53, 0x49, 0x53, 0x10, 0x04, 0x42, 0x09, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x42, 0x58, 0x0a, 0x10, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x42, 0x0f, 0x44, 0x61, 0x74, 0x61, 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_DataSource_proto_rawDescOnce sync.Once + file_feast_core_DataSource_proto_rawDescData = file_feast_core_DataSource_proto_rawDesc +) + +func file_feast_core_DataSource_proto_rawDescGZIP() []byte { + file_feast_core_DataSource_proto_rawDescOnce.Do(func() { + file_feast_core_DataSource_proto_rawDescData = protoimpl.X.CompressGZIP(file_feast_core_DataSource_proto_rawDescData) + }) + return file_feast_core_DataSource_proto_rawDescData +} + +var file_feast_core_DataSource_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_feast_core_DataSource_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_feast_core_DataSource_proto_goTypes = []interface{}{ + (DataSource_SourceType)(0), // 0: feast.core.DataSource.SourceType + (*DataSource)(nil), // 1: feast.core.DataSource + nil, // 2: feast.core.DataSource.FieldMappingEntry + (*DataSource_FileOptions)(nil), // 3: feast.core.DataSource.FileOptions + (*DataSource_BigQueryOptions)(nil), // 4: feast.core.DataSource.BigQueryOptions + (*DataSource_KafkaOptions)(nil), // 5: feast.core.DataSource.KafkaOptions + (*DataSource_KinesisOptions)(nil), // 6: feast.core.DataSource.KinesisOptions +} +var file_feast_core_DataSource_proto_depIdxs = []int32{ + 0, // 0: feast.core.DataSource.type:type_name -> feast.core.DataSource.SourceType + 2, // 1: feast.core.DataSource.field_mapping:type_name -> feast.core.DataSource.FieldMappingEntry + 3, // 2: feast.core.DataSource.file_options:type_name -> feast.core.DataSource.FileOptions + 4, // 3: feast.core.DataSource.bigquery_options:type_name -> feast.core.DataSource.BigQueryOptions + 5, // 4: feast.core.DataSource.kafka_options:type_name -> feast.core.DataSource.KafkaOptions + 6, // 5: feast.core.DataSource.kinesis_options:type_name -> feast.core.DataSource.KinesisOptions + 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_DataSource_proto_init() } +func file_feast_core_DataSource_proto_init() { + if File_feast_core_DataSource_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_feast_core_DataSource_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DataSource); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_DataSource_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DataSource_FileOptions); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_DataSource_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DataSource_BigQueryOptions); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_DataSource_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DataSource_KafkaOptions); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_DataSource_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DataSource_KinesisOptions); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_feast_core_DataSource_proto_msgTypes[0].OneofWrappers = []interface{}{ + (*DataSource_FileOptions_)(nil), + (*DataSource_BigqueryOptions)(nil), + (*DataSource_KafkaOptions_)(nil), + (*DataSource_KinesisOptions_)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_feast_core_DataSource_proto_rawDesc, + NumEnums: 1, + NumMessages: 6, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_feast_core_DataSource_proto_goTypes, + DependencyIndexes: file_feast_core_DataSource_proto_depIdxs, + EnumInfos: file_feast_core_DataSource_proto_enumTypes, + MessageInfos: file_feast_core_DataSource_proto_msgTypes, + }.Build() + File_feast_core_DataSource_proto = out.File + file_feast_core_DataSource_proto_rawDesc = nil + file_feast_core_DataSource_proto_goTypes = nil + file_feast_core_DataSource_proto_depIdxs = nil +} diff --git a/sdk/go/protos/feast/core/Feature.pb.go b/sdk/go/protos/feast/core/Feature.pb.go new file mode 100644 index 00000000000..1ad93ef8a1c --- /dev/null +++ b/sdk/go/protos/feast/core/Feature.pb.go @@ -0,0 +1,205 @@ +// +// 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.25.0 +// protoc v3.12.4 +// source: feast/core/Feature.proto + +package core + +import ( + types "github.com/feast-dev/feast/sdk/go/protos/feast/types" + 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 FeatureSpecV2 struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Name of the feature. Not updatable. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Value type of the feature. Not updatable. + 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,3,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *FeatureSpecV2) Reset() { + *x = FeatureSpecV2{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_Feature_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FeatureSpecV2) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FeatureSpecV2) ProtoMessage() {} + +func (x *FeatureSpecV2) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_Feature_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 FeatureSpecV2.ProtoReflect.Descriptor instead. +func (*FeatureSpecV2) Descriptor() ([]byte, []int) { + return file_feast_core_Feature_proto_rawDescGZIP(), []int{0} +} + +func (x *FeatureSpecV2) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *FeatureSpecV2) GetValueType() types.ValueType_Enum { + if x != nil { + return x.ValueType + } + return types.ValueType_INVALID +} + +func (x *FeatureSpecV2) GetLabels() map[string]string { + if x != nil { + return x.Labels + } + return nil +} + +var File_feast_core_Feature_proto protoreflect.FileDescriptor + +var file_feast_core_Feature_proto_rawDesc = []byte{ + 0x0a, 0x18, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x46, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 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, 0x22, + 0xd9, 0x01, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x70, 0x65, 0x63, 0x56, + 0x32, 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, 0x3d, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x25, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x46, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x70, 0x65, 0x63, 0x56, 0x32, 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, 0x42, 0x55, 0x0a, 0x10, 0x66, + 0x65, 0x61, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x42, + 0x0c, 0x46, 0x65, 0x61, 0x74, 0x75, 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_Feature_proto_rawDescOnce sync.Once + file_feast_core_Feature_proto_rawDescData = file_feast_core_Feature_proto_rawDesc +) + +func file_feast_core_Feature_proto_rawDescGZIP() []byte { + file_feast_core_Feature_proto_rawDescOnce.Do(func() { + file_feast_core_Feature_proto_rawDescData = protoimpl.X.CompressGZIP(file_feast_core_Feature_proto_rawDescData) + }) + return file_feast_core_Feature_proto_rawDescData +} + +var file_feast_core_Feature_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_feast_core_Feature_proto_goTypes = []interface{}{ + (*FeatureSpecV2)(nil), // 0: feast.core.FeatureSpecV2 + nil, // 1: feast.core.FeatureSpecV2.LabelsEntry + (types.ValueType_Enum)(0), // 2: feast.types.ValueType.Enum +} +var file_feast_core_Feature_proto_depIdxs = []int32{ + 2, // 0: feast.core.FeatureSpecV2.value_type:type_name -> feast.types.ValueType.Enum + 1, // 1: feast.core.FeatureSpecV2.labels:type_name -> feast.core.FeatureSpecV2.LabelsEntry + 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_Feature_proto_init() } +func file_feast_core_Feature_proto_init() { + if File_feast_core_Feature_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_feast_core_Feature_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FeatureSpecV2); 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_Feature_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_feast_core_Feature_proto_goTypes, + DependencyIndexes: file_feast_core_Feature_proto_depIdxs, + MessageInfos: file_feast_core_Feature_proto_msgTypes, + }.Build() + File_feast_core_Feature_proto = out.File + file_feast_core_Feature_proto_rawDesc = nil + file_feast_core_Feature_proto_goTypes = nil + file_feast_core_Feature_proto_depIdxs = nil +} diff --git a/sdk/go/protos/feast/core/FeatureTable.pb.go b/sdk/go/protos/feast/core/FeatureTable.pb.go new file mode 100644 index 00000000000..290477931d0 --- /dev/null +++ b/sdk/go/protos/feast/core/FeatureTable.pb.go @@ -0,0 +1,450 @@ +// +// 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.25.0 +// protoc v3.12.4 +// source: feast/core/FeatureTable.proto + +package core + +import ( + proto "github.com/golang/protobuf/proto" + duration "github.com/golang/protobuf/ptypes/duration" + timestamp "github.com/golang/protobuf/ptypes/timestamp" + 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 FeatureTable struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // User-specified specifications of this feature table. + Spec *FeatureTableSpec `protobuf:"bytes,1,opt,name=spec,proto3" json:"spec,omitempty"` + // System-populated metadata for this feature table. + Meta *FeatureTableMeta `protobuf:"bytes,2,opt,name=meta,proto3" json:"meta,omitempty"` +} + +func (x *FeatureTable) Reset() { + *x = FeatureTable{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_FeatureTable_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FeatureTable) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FeatureTable) ProtoMessage() {} + +func (x *FeatureTable) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_FeatureTable_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 FeatureTable.ProtoReflect.Descriptor instead. +func (*FeatureTable) Descriptor() ([]byte, []int) { + return file_feast_core_FeatureTable_proto_rawDescGZIP(), []int{0} +} + +func (x *FeatureTable) GetSpec() *FeatureTableSpec { + if x != nil { + return x.Spec + } + return nil +} + +func (x *FeatureTable) GetMeta() *FeatureTableMeta { + if x != nil { + return x.Meta + } + return nil +} + +type FeatureTableSpec struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Name of the feature set. Must be unique. Not updated. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // List names of entities to associate with the Features defined in this + // Feature Table. Not updatable. + Entities []string `protobuf:"bytes,3,rep,name=entities,proto3" json:"entities,omitempty"` + // List of features specifications for each feature defined with this feature table. + Features []*FeatureSpecV2 `protobuf:"bytes,4,rep,name=features,proto3" json:"features,omitempty"` + // User defined metadata + Labels map[string]string `protobuf:"bytes,5,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // Features in this feature table can only be retrieved from online serving + // younger than max age. Age is measured as the duration of time between + // the feature's event timestamp and when the feature is retrieved + // Feature values outside max age will be returned as unset values and indicated to end user + MaxAge *duration.Duration `protobuf:"bytes,6,opt,name=max_age,json=maxAge,proto3" json:"max_age,omitempty"` + // Batch/Offline DataSource to source batch/offline feature data. + // Only batch DataSource can be specified + // (ie source type should start with 'BATCH_') + BatchSource *DataSource `protobuf:"bytes,7,opt,name=batch_source,json=batchSource,proto3" json:"batch_source,omitempty"` + // Stream/Online DataSource to source stream/online feature data. + // Only stream DataSource can be specified + // (ie source type should start with 'STREAM_') + StreamSource *DataSource `protobuf:"bytes,8,opt,name=stream_source,json=streamSource,proto3" json:"stream_source,omitempty"` +} + +func (x *FeatureTableSpec) Reset() { + *x = FeatureTableSpec{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_FeatureTable_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FeatureTableSpec) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FeatureTableSpec) ProtoMessage() {} + +func (x *FeatureTableSpec) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_FeatureTable_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 FeatureTableSpec.ProtoReflect.Descriptor instead. +func (*FeatureTableSpec) Descriptor() ([]byte, []int) { + return file_feast_core_FeatureTable_proto_rawDescGZIP(), []int{1} +} + +func (x *FeatureTableSpec) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *FeatureTableSpec) GetEntities() []string { + if x != nil { + return x.Entities + } + return nil +} + +func (x *FeatureTableSpec) GetFeatures() []*FeatureSpecV2 { + if x != nil { + return x.Features + } + return nil +} + +func (x *FeatureTableSpec) GetLabels() map[string]string { + if x != nil { + return x.Labels + } + return nil +} + +func (x *FeatureTableSpec) GetMaxAge() *duration.Duration { + if x != nil { + return x.MaxAge + } + return nil +} + +func (x *FeatureTableSpec) GetBatchSource() *DataSource { + if x != nil { + return x.BatchSource + } + return nil +} + +func (x *FeatureTableSpec) GetStreamSource() *DataSource { + if x != nil { + return x.StreamSource + } + return nil +} + +type FeatureTableMeta struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Time where this Feature Table is created + CreatedTimestamp *timestamp.Timestamp `protobuf:"bytes,1,opt,name=created_timestamp,json=createdTimestamp,proto3" json:"created_timestamp,omitempty"` + // Time where this Feature Table is last updated + LastUpdatedTimestamp *timestamp.Timestamp `protobuf:"bytes,2,opt,name=last_updated_timestamp,json=lastUpdatedTimestamp,proto3" json:"last_updated_timestamp,omitempty"` + // Auto incrementing revision no. of this Feature Table + Revision int64 `protobuf:"varint,3,opt,name=revision,proto3" json:"revision,omitempty"` +} + +func (x *FeatureTableMeta) Reset() { + *x = FeatureTableMeta{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_FeatureTable_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FeatureTableMeta) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FeatureTableMeta) ProtoMessage() {} + +func (x *FeatureTableMeta) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_FeatureTable_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 FeatureTableMeta.ProtoReflect.Descriptor instead. +func (*FeatureTableMeta) Descriptor() ([]byte, []int) { + return file_feast_core_FeatureTable_proto_rawDescGZIP(), []int{2} +} + +func (x *FeatureTableMeta) GetCreatedTimestamp() *timestamp.Timestamp { + if x != nil { + return x.CreatedTimestamp + } + return nil +} + +func (x *FeatureTableMeta) GetLastUpdatedTimestamp() *timestamp.Timestamp { + if x != nil { + return x.LastUpdatedTimestamp + } + return nil +} + +func (x *FeatureTableMeta) GetRevision() int64 { + if x != nil { + return x.Revision + } + return 0 +} + +var File_feast_core_FeatureTable_proto protoreflect.FileDescriptor + +var file_feast_core_FeatureTable_proto_rawDesc = []byte{ + 0x0a, 0x1d, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x46, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x0a, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x1b, 0x66, 0x65, + 0x61, 0x73, 0x74, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x18, 0x66, 0x65, 0x61, 0x73, 0x74, + 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x22, 0x72, 0x0a, 0x0c, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x54, 0x61, + 0x62, 0x6c, 0x65, 0x12, 0x30, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1c, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x46, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x70, 0x65, 0x63, 0x52, + 0x04, 0x73, 0x70, 0x65, 0x63, 0x12, 0x30, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, + 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x4d, 0x65, 0x74, + 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x22, 0xa2, 0x03, 0x0a, 0x10, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x54, 0x61, 0x62, 0x6c, 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, 0x1a, 0x0a, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x35, 0x0a, 0x08, + 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, + 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x53, 0x70, 0x65, 0x63, 0x56, 0x32, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x73, 0x12, 0x40, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x05, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, + 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x54, 0x61, 0x62, 0x6c, 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, 0x32, 0x0a, 0x07, 0x6d, 0x61, 0x78, 0x5f, 0x61, 0x67, 0x65, + 0x18, 0x06, 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, 0x39, 0x0a, 0x0c, 0x62, 0x61, 0x74, + 0x63, 0x68, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x16, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x44, 0x61, 0x74, + 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0b, 0x62, 0x61, 0x74, 0x63, 0x68, 0x53, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x12, 0x3b, 0x0a, 0x0d, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x66, 0x65, + 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x52, 0x0c, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x6f, 0x75, 0x72, 0x63, + 0x65, 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, 0x22, 0xc9, 0x01, 0x0a, + 0x10, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 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, 0x50, 0x0a, 0x16, 0x6c, 0x61, + 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x18, 0x02, 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, 0x14, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1a, 0x0a, 0x08, + 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, + 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x5a, 0x0a, 0x10, 0x66, 0x65, 0x61, 0x73, + 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x42, 0x11, 0x46, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x54, 0x61, 0x62, 0x6c, 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_FeatureTable_proto_rawDescOnce sync.Once + file_feast_core_FeatureTable_proto_rawDescData = file_feast_core_FeatureTable_proto_rawDesc +) + +func file_feast_core_FeatureTable_proto_rawDescGZIP() []byte { + file_feast_core_FeatureTable_proto_rawDescOnce.Do(func() { + file_feast_core_FeatureTable_proto_rawDescData = protoimpl.X.CompressGZIP(file_feast_core_FeatureTable_proto_rawDescData) + }) + return file_feast_core_FeatureTable_proto_rawDescData +} + +var file_feast_core_FeatureTable_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_feast_core_FeatureTable_proto_goTypes = []interface{}{ + (*FeatureTable)(nil), // 0: feast.core.FeatureTable + (*FeatureTableSpec)(nil), // 1: feast.core.FeatureTableSpec + (*FeatureTableMeta)(nil), // 2: feast.core.FeatureTableMeta + nil, // 3: feast.core.FeatureTableSpec.LabelsEntry + (*FeatureSpecV2)(nil), // 4: feast.core.FeatureSpecV2 + (*duration.Duration)(nil), // 5: google.protobuf.Duration + (*DataSource)(nil), // 6: feast.core.DataSource + (*timestamp.Timestamp)(nil), // 7: google.protobuf.Timestamp +} +var file_feast_core_FeatureTable_proto_depIdxs = []int32{ + 1, // 0: feast.core.FeatureTable.spec:type_name -> feast.core.FeatureTableSpec + 2, // 1: feast.core.FeatureTable.meta:type_name -> feast.core.FeatureTableMeta + 4, // 2: feast.core.FeatureTableSpec.features:type_name -> feast.core.FeatureSpecV2 + 3, // 3: feast.core.FeatureTableSpec.labels:type_name -> feast.core.FeatureTableSpec.LabelsEntry + 5, // 4: feast.core.FeatureTableSpec.max_age:type_name -> google.protobuf.Duration + 6, // 5: feast.core.FeatureTableSpec.batch_source:type_name -> feast.core.DataSource + 6, // 6: feast.core.FeatureTableSpec.stream_source:type_name -> feast.core.DataSource + 7, // 7: feast.core.FeatureTableMeta.created_timestamp:type_name -> google.protobuf.Timestamp + 7, // 8: feast.core.FeatureTableMeta.last_updated_timestamp:type_name -> google.protobuf.Timestamp + 9, // [9:9] is the sub-list for method output_type + 9, // [9:9] is the sub-list for method input_type + 9, // [9:9] is the sub-list for extension type_name + 9, // [9:9] is the sub-list for extension extendee + 0, // [0:9] is the sub-list for field type_name +} + +func init() { file_feast_core_FeatureTable_proto_init() } +func file_feast_core_FeatureTable_proto_init() { + if File_feast_core_FeatureTable_proto != nil { + return + } + file_feast_core_DataSource_proto_init() + file_feast_core_Feature_proto_init() + if !protoimpl.UnsafeEnabled { + file_feast_core_FeatureTable_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FeatureTable); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_FeatureTable_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FeatureTableSpec); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_core_FeatureTable_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FeatureTableMeta); 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_FeatureTable_proto_rawDesc, + NumEnums: 0, + NumMessages: 4, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_feast_core_FeatureTable_proto_goTypes, + DependencyIndexes: file_feast_core_FeatureTable_proto_depIdxs, + MessageInfos: file_feast_core_FeatureTable_proto_msgTypes, + }.Build() + File_feast_core_FeatureTable_proto = out.File + file_feast_core_FeatureTable_proto_rawDesc = nil + file_feast_core_FeatureTable_proto_goTypes = nil + file_feast_core_FeatureTable_proto_depIdxs = nil +} diff --git a/sdk/python/feast/cli.py b/sdk/python/feast/cli.py index 2c6e6fced37..1c774ea89f5 100644 --- a/sdk/python/feast/cli.py +++ b/sdk/python/feast/cli.py @@ -27,6 +27,7 @@ from feast.core.IngestionJob_pb2 import IngestionJobStatus from feast.entity import EntityV2 from feast.feature_set import FeatureSet, FeatureSetRef +from feast.feature_table import FeatureTable from feast.loaders.yaml import yaml_loader _logger = logging.getLogger(__name__) @@ -208,6 +209,88 @@ def entity_list(project: str, labels: str): print(tabulate(table, headers=["NAME", "DESCRIPTION", "TYPE"], tablefmt="plain")) +@cli.group(name="feature-tables") +def feature_table(): + """ + Create and manage feature tables + """ + pass + + +@feature_table.command("apply") +@click.option( + "--filename", + "-f", + help="Path to a feature table configuration file that will be applied", + type=click.Path(exists=True), +) +def feature_table_create(filename): + """ + Create or update a feature table + """ + + feature_tables = [ + FeatureTable.from_dict(ft_dict) for ft_dict in yaml_loader(filename) + ] + feast_client = Client() # type: Client + feast_client.apply_feature_table(feature_tables) + + +@feature_table.command("describe") +@click.argument("name", type=click.STRING) +@click.option( + "--project", + "-p", + help="Project that feature table belongs to", + type=click.STRING, + default="default", +) +def feature_table_describe(name: str, project: str): + """ + Describe a feature table + """ + feast_client = Client() # type: Client + ft = feast_client.get_feature_table(name=name, project=project) + + if not ft: + print(f'Feature table with name "{name}" could not be found') + return + + print(yaml.dump(yaml.safe_load(str(ft)), default_flow_style=False, sort_keys=False)) + + +@feature_table.command(name="list") +@click.option( + "--project", + "-p", + help="Project that feature table belongs to", + type=click.STRING, + default="", +) +@click.option( + "--labels", + "-l", + help="Labels to filter for feature tables", + type=click.STRING, + default="", +) +def feature_table_list(project: str, labels: str): + """ + List all feature tables + """ + feast_client = Client() # type: Client + + labels_dict = _get_labels_dict(labels) + + table = [] + for ft in feast_client.list_feature_tables(project=project, labels=labels_dict): + table.append([ft.name, ft.entities]) + + from tabulate import tabulate + + print(tabulate(table, headers=["NAME", "ENTITIES"], tablefmt="plain")) + + @cli.group(name="features") def feature(): """ diff --git a/sdk/python/feast/client.py b/sdk/python/feast/client.py index 6076be7dbef..713776f1f53 100644 --- a/sdk/python/feast/client.py +++ b/sdk/python/feast/client.py @@ -46,6 +46,8 @@ ApplyEntityResponse, ApplyFeatureSetRequest, ApplyFeatureSetResponse, + ApplyFeatureTableRequest, + ApplyFeatureTableResponse, ArchiveProjectRequest, ArchiveProjectResponse, CreateProjectRequest, @@ -56,12 +58,16 @@ GetFeatureSetRequest, GetFeatureSetResponse, GetFeatureStatisticsRequest, + GetFeatureTableRequest, + GetFeatureTableResponse, ListEntitiesRequest, ListEntitiesResponse, ListFeatureSetsRequest, ListFeatureSetsResponse, ListFeaturesRequest, ListFeaturesResponse, + ListFeatureTablesRequest, + ListFeatureTablesResponse, ListProjectsRequest, ListProjectsResponse, ) @@ -70,6 +76,7 @@ from feast.entity import EntityV2 from feast.feature import Feature, FeatureRef from feast.feature_set import FeatureSet +from feast.feature_table import FeatureTable from feast.grpc import auth as feast_auth from feast.grpc.grpc import create_grpc_channel from feast.job import RetrievalJob @@ -485,6 +492,119 @@ def get_entity(self, name: str, project: str = None) -> Union[EntityV2, None]: return entity + def apply_feature_table( + self, + feature_tables: Union[List[FeatureTable], FeatureTable], + project: str = None, + ): + """ + Idempotently registers feature tables with Feast Core. Either a single + feature table or a list can be provided. + + Args: + feature_tables: List of feature tables that will be registered + """ + + if project is None: + project = self.project + + if not isinstance(feature_tables, list): + feature_tables = [feature_tables] + for feature_table in feature_tables: + if isinstance(feature_table, FeatureTable): + self._apply_feature_table(project, feature_table) # type: ignore + continue + raise ValueError( + f"Could not determine feature table type to apply {feature_table}" + ) + + def _apply_feature_table(self, project: str, feature_table: FeatureTable): + """ + Registers a single feature table with Feast + + Args: + feature_table: Feature table that will be registered + """ + + feature_table.is_valid() + feature_table_proto = feature_table.to_spec_proto() + + # Convert the feature table to a request and send to Feast Core + try: + apply_feature_table_response = self._core_service.ApplyFeatureTable( + ApplyFeatureTableRequest(project=project, table_spec=feature_table_proto), # type: ignore + timeout=self._config.getint(CONFIG_GRPC_CONNECTION_TIMEOUT_DEFAULT_KEY), + metadata=self._get_grpc_metadata(), + ) # type: ApplyFeatureTableResponse + except grpc.RpcError as e: + raise grpc.RpcError(e.details()) + + # Extract the returned feature table + applied_feature_table = FeatureTable.from_proto( + apply_feature_table_response.table + ) + + # Deep copy from the returned feature table to the local entity + feature_table._update_from_feature_table(applied_feature_table) + + def list_feature_tables( + self, project: str = None, labels: Dict[str, str] = dict() + ) -> List[FeatureTable]: + """ + Retrieve a list of feature tables from Feast Core + + Args: + project: Filter feature tables based on project name + + Returns: + List of feature tables + """ + + if project is None: + project = self.project + + filter = ListFeatureTablesRequest.Filter(project=project, labels=labels) + + # Get latest feature tables from Feast Core + feature_table_protos = self._core_service.ListFeatureTables( + ListFeatureTablesRequest(filter=filter), metadata=self._get_grpc_metadata(), + ) # type: ListFeatureTablesResponse + + # Extract feature tables and return + feature_tables = [] + for feature_table_proto in feature_table_protos.tables: + feature_table = FeatureTable.from_proto(feature_table_proto) + feature_table._client = self + feature_tables.append(feature_table) + return feature_tables + + def get_feature_table( + self, name: str, project: str = None + ) -> Union[FeatureTable, None]: + """ + Retrieves a feature table. + + Args: + project: Feast project that this feature table belongs to + name: Name of feature table + + Returns: + Returns either the specified feature table, or raises an exception if + none is found + """ + + if project is None: + project = self.project + + try: + get_feature_table_response = self._core_service.GetFeatureTable( + GetFeatureTableRequest(project=project, name=name.strip()), + metadata=self._get_grpc_metadata(), + ) # type: GetFeatureTableResponse + except grpc.RpcError as e: + raise grpc.RpcError(e.details()) + return FeatureTable.from_proto(get_feature_table_response.table) + def apply(self, feature_sets: Union[List[FeatureSet], FeatureSet]): """ Idempotently registers feature set(s) with Feast Core. Either a single diff --git a/sdk/python/feast/data_source.py b/sdk/python/feast/data_source.py new file mode 100644 index 00000000000..59020f8ec94 --- /dev/null +++ b/sdk/python/feast/data_source.py @@ -0,0 +1,512 @@ +# 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 enum +from typing import Dict, Optional, Union + +from feast.core.DataSource_pb2 import DataSource as DataSourceProto + + +class SourceType(enum.Enum): + """ + DataSource value type. Used to define source types in DataSource. + """ + + UNKNOWN = 0 + BATCH_FILE = 1 + BATCH_BIGQUERY = 2 + STREAM_KAFKA = 3 + STREAM_KINESIS = 4 + + +class FileOptions: + """ + DataSource File options used to source features from a file + """ + + def __init__( + self, file_format: str, file_url: str, + ): + self._file_format = file_format + self._file_url = file_url + + @property + def file_format(self): + """ + Returns the file format of this file + """ + return self._file_format + + @file_format.setter + def file_format(self, file_format): + """ + Sets the file format of this file + """ + self._file_format = file_format + + @property + def file_url(self): + """ + Returns the file url of this file + """ + return self._file_url + + @file_url.setter + def file_url(self, file_url): + """ + Sets the file url of this file + """ + self._file_url = file_url + + @classmethod + def from_proto(cls, file_options_proto: DataSourceProto.FileOptions): + """ + Creates a FileOptions from a protobuf representation of a file option + + Args: + file_options_proto: A protobuf representation of a DataSource + + Returns: + Returns a FileOptions object based on the file_options protobuf + """ + + file_options = cls( + file_format=file_options_proto.file_format, + file_url=file_options_proto.file_url, + ) + + return file_options + + def to_proto(self) -> DataSourceProto.FileOptions: + """ + Converts an FileOptionsProto object to its protobuf representation. + + Returns: + FileOptionsProto protobuf + """ + + file_options_proto = DataSourceProto.FileOptions( + file_format=self.file_format, file_url=self.file_url, + ) + + return file_options_proto + + +class BigQueryOptions: + """ + DataSource BigQuery options used to source features from BigQuery query + """ + + def __init__( + self, table_ref: str, + ): + self._table_ref = table_ref + + @property + def table_ref(self): + """ + Returns the table ref of this BQ table + """ + return self._table_ref + + @table_ref.setter + def table_ref(self, table_ref): + """ + Sets the table ref of this BQ table + """ + self._table_ref = table_ref + + @classmethod + def from_proto(cls, bigquery_options_proto: DataSourceProto.BigQueryOptions): + """ + Creates a BigQueryOptions from a protobuf representation of a BigQuery option + + Args: + bigquery_options_proto: A protobuf representation of a DataSource + + Returns: + Returns a BigQueryOptions object based on the bigquery_options protobuf + """ + + bigquery_options = cls(table_ref=bigquery_options_proto.table_ref,) + + return bigquery_options + + def to_proto(self) -> DataSourceProto.BigQueryOptions: + """ + Converts an BigQueryOptionsProto object to its protobuf representation. + + Returns: + BigQueryOptionsProto protobuf + """ + + bigquery_options_proto = DataSourceProto.BigQueryOptions( + table_ref=self.table_ref, + ) + + return bigquery_options_proto + + +class KafkaOptions: + """ + DataSource Kafka options used to source features from Kafka messages + """ + + def __init__( + self, bootstrap_servers: str, class_path: str, topic: str, + ): + self._bootstrap_servers = bootstrap_servers + self._class_path = class_path + self._topic = topic + + @property + def bootstrap_servers(self): + """ + Returns a comma-separated list of Kafka bootstrap servers + """ + return self._bootstrap_servers + + @bootstrap_servers.setter + def bootstrap_servers(self, bootstrap_servers): + """ + Sets a comma-separated list of Kafka bootstrap servers + """ + self._bootstrap_servers = bootstrap_servers + + @property + def class_path(self): + """ + Returns the class path to the generated Java Protobuf class that can be + used to decode feature data from the obtained Kafka message + """ + return self._class_path + + @class_path.setter + def class_path(self, class_path): + """ + Sets the class path to the generated Java Protobuf class that can be + used to decode feature data from the obtained Kafka message + """ + self._class_path = class_path + + @property + def topic(self): + """ + Returns the Kafka topic to collect feature data from + """ + return self._topic + + @topic.setter + def topic(self, topic): + """ + Sets the Kafka topic to collect feature data from + """ + self._topic = topic + + @classmethod + def from_proto(cls, kafka_options_proto: DataSourceProto.KafkaOptions): + """ + Creates a KafkaOptions from a protobuf representation of a kafka option + + Args: + kafka_options_proto: A protobuf representation of a DataSource + + Returns: + Returns a BigQueryOptions object based on the kafka_options protobuf + """ + + kafka_options = cls( + bootstrap_servers=kafka_options_proto.bootstrap_servers, + class_path=kafka_options_proto.class_path, + topic=kafka_options_proto.topic, + ) + + return kafka_options + + def to_proto(self) -> DataSourceProto.KafkaOptions: + """ + Converts an KafkaOptionsProto object to its protobuf representation. + + Returns: + KafkaOptionsProto protobuf + """ + + kafka_options_proto = DataSourceProto.KafkaOptions( + bootstrap_servers=self.bootstrap_servers, + class_path=self.class_path, + topic=self.topic, + ) + + return kafka_options_proto + + +class KinesisOptions: + """ + DataSource Kinesis options used to source features from Kinesis records + """ + + def __init__( + self, class_path: str, region: str, stream_name: str, + ): + self._class_path = class_path + self._region = region + self._stream_name = stream_name + + @property + def class_path(self): + """ + Returns the class path to the generated Java Protobuf class that can be + used to decode feature data from the obtained Kinesis record + """ + return self._class_path + + @class_path.setter + def class_path(self, class_path): + """ + Sets the class path to the generated Java Protobuf class that can be + used to decode feature data from the obtained Kinesis record + """ + self._class_path = class_path + + @property + def region(self): + """ + Returns the AWS region of Kinesis stream + """ + return self._region + + @region.setter + def region(self, region): + """ + Sets the AWS region of Kinesis stream + """ + self._region = region + + @property + def stream_name(self): + """ + Returns the Kinesis stream name to obtain feature data from + """ + return self._stream_name + + @stream_name.setter + def stream_name(self, stream_name): + """ + Sets the Kinesis stream name to obtain feature data from + """ + self._stream_name = stream_name + + @classmethod + def from_proto(cls, kinesis_options_proto: DataSourceProto.KinesisOptions): + """ + Creates a KinesisOptions from a protobuf representation of a kinesis option + + Args: + kinesis_options_proto: A protobuf representation of a DataSource + + Returns: + Returns a KinesisOptions object based on the kinesis_options protobuf + """ + + kinesis_options = cls( + class_path=kinesis_options_proto.class_path, + region=kinesis_options_proto.region, + stream_name=kinesis_options_proto.stream_name, + ) + + return kinesis_options + + def to_proto(self) -> DataSourceProto.KinesisOptions: + """ + Converts an KinesisOptionsProto object to its protobuf representation. + + Returns: + KinesisOptionsProto protobuf + """ + + kinesis_options_proto = DataSourceProto.KinesisOptions( + class_path=self.class_path, + region=self.region, + stream_name=self.stream_name, + ) + + return kinesis_options_proto + + +class DataSource: + """ + DataSource that can be used source features + """ + + def __init__( + self, + type: str, + field_mapping: Dict[str, str], + options: Union[BigQueryOptions, FileOptions, KafkaOptions, KinesisOptions], + timestamp_column: str, + date_partition_column: Optional[str] = "", + ): + self._type = type + self._field_mapping = field_mapping + self._options = options + self._timestamp_column = timestamp_column + self._date_partition_column = date_partition_column + + @property + def type(self): + """ + Returns the type of this data source + """ + return self._type + + @type.setter + def type(self, type): + """ + Sets the type of this data source + """ + self._type = type + + @property + def field_mapping(self): + """ + Returns the field mapping of this data source + """ + return self._field_mapping + + @field_mapping.setter + def field_mapping(self, field_mapping): + """ + Sets the field mapping of this data source + """ + self._field_mapping = field_mapping + + @property + def options(self): + """ + Returns the options of this data source + """ + return self._options + + @options.setter + def options(self, options): + """ + Sets the options of this data source + """ + self._options = options + + @property + def timestamp_column(self): + """ + Returns the timestamp column of this data source + """ + return self._timestamp_column + + @timestamp_column.setter + def timestamp_column(self, timestamp_column): + """ + Sets the timestamp column of this data source + """ + self._timestamp_column = timestamp_column + + @property + def date_partition_column(self): + """ + Returns the date partition column of this data source + """ + return self._date_partition_column + + @date_partition_column.setter + def date_partition_column(self, date_partition_column): + """ + Sets the date partition column of this data source + """ + self._date_partition_column = date_partition_column + + @classmethod + def from_proto(cls, data_source_proto: DataSourceProto): + """ + Creates a DataSource from a protobuf representation of an data source + + Args: + data_source_proto: A protobuf representation of a DataSource + + Returns: + Returns a DataSource object based on the data_source protobuf + """ + + if isinstance(cls.options, FileOptions): + data_source = cls(file_options=data_source_proto.options,) + if isinstance(cls.options, BigQueryOptions): + data_source = cls(bigquery_options=data_source_proto.options,) + if isinstance(cls.options, KafkaOptions): + data_source = cls(kafka_options=data_source_proto.options,) + if isinstance(cls.options, KinesisOptions): + data_source = cls(kinesis_options=data_source_proto.options,) + else: + raise TypeError( + "DataSource.from_proto: Provided DataSource option is invalid. Only FileOptions, BigQueryOptions, KafkaOptions and KinesisOptions are supported currently." + ) + + data_source = cls( + type=data_source_proto.type, + field_mapping=data_source_proto.field_mapping, + timestamp_column=data_source_proto.timestamp_column, + date_partition_column=data_source_proto.date_partition_column, + ) + + return data_source + + def to_proto(self) -> DataSourceProto: + """ + Converts an DataSourceProto object to its protobuf representation. + Used when passing DataSourceProto object to Feast request. + + Returns: + DataSourceProto protobuf + """ + + if isinstance(self.options, FileOptions): + data_source_proto = DataSourceProto( + type=self.type, + field_mapping=self.field_mapping, + file_options=self.options.to_proto(), + ) + elif isinstance(self.options, BigQueryOptions): + data_source_proto = DataSourceProto( + type=self.type, + field_mapping=self.field_mapping, + bigquery_options=self.options.to_proto(), + ) + elif isinstance(self.options, KafkaOptions): + data_source_proto = DataSourceProto( + type=self.type, + field_mapping=self.field_mapping, + kafka_options=self.options.to_proto(), + ) + elif isinstance(self.options, KinesisOptions): + data_source_proto = DataSourceProto( + type=self.type, + field_mapping=self.field_mapping, + kinesis_options=self.options.to_proto(), + ) + else: + raise TypeError( + "DataSource.to_proto: Provided DataSource option is invalid. Only FileOptions, BigQueryOptions, KafkaOptions and KinesisOptions are supported currently." + ) + + data_source_proto.timestamp_column = self.timestamp_column + data_source_proto.date_partition_column = self.date_partition_column + + return data_source_proto diff --git a/sdk/python/feast/feature_table.py b/sdk/python/feast/feature_table.py new file mode 100644 index 00000000000..6e73df78c37 --- /dev/null +++ b/sdk/python/feast/feature_table.py @@ -0,0 +1,434 @@ +# 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. + +from typing import Dict, List, MutableMapping, Optional, Union + +import yaml +from google.protobuf import json_format +from google.protobuf.duration_pb2 import Duration +from google.protobuf.json_format import MessageToDict, MessageToJson +from google.protobuf.timestamp_pb2 import Timestamp + +from feast.core.FeatureTable_pb2 import FeatureTable as FeatureTableProto +from feast.core.FeatureTable_pb2 import FeatureTableMeta as FeatureTableMetaProto +from feast.core.FeatureTable_pb2 import FeatureTableSpec as FeatureTableSpecProto +from feast.data_source import ( + BigQueryOptions, + DataSource, + FileOptions, + KafkaOptions, + KinesisOptions, + SourceType, +) +from feast.feature_v2 import FeatureV2 +from feast.loaders import yaml as feast_yaml + + +class FeatureTable: + """ + Represents a collection of features and associated metadata. + """ + + def __init__( + self, + name: str, + entities: Union[str, List[str]], + features: Union[FeatureV2, List[FeatureV2]], + batch_source: Optional[DataSource] = None, + stream_source: Optional[DataSource] = None, + max_age: Optional[Duration] = None, + labels: Optional[MutableMapping[str, str]] = None, + ): + self._name = name + self._entities = entities + self._features = features + self._batch_source = batch_source + self._stream_source = stream_source + if labels is None: + self._labels = dict() # type: MutableMapping[str, str] + else: + self._labels = labels + + self._max_age = max_age + self._created_timestamp: Optional[Timestamp] = None + self._last_updated_timestamp: Optional[Timestamp] = None + + def __str__(self): + return str(MessageToJson(self.to_proto())) + + def __eq__(self, other): + if not isinstance(other, FeatureTable): + raise TypeError( + "Comparisons should only involve FeatureTable class objects." + ) + + if ( + self.labels != other.labels + or self.name != other.name + or self.max_age != other.max_age + ): + return False + + if self.entities != other.entities: + return False + if self.batch_source != other.batch_source: + return False + if self.stream_source != other.stream_source: + return False + + return True + + @property + def name(self): + """ + Returns the name of this feature table + """ + return self._name + + @name.setter + def name(self, name): + """ + Sets the name of this feature table + """ + self._name = name + + @property + def entities(self): + """ + Returns the entities of this feature table + """ + return self._entities + + @entities.setter + def entities(self, entities): + """ + Sets the entities of this feature table + """ + self._entities = entities + + @property + def features(self): + """ + Returns the features of this feature table + """ + return self._features + + @features.setter + def features(self, features): + """ + Sets the features of this feature table + """ + self._features = features + + @property + def batch_source(self): + """ + Returns the batch source of this feature table + """ + return self._batch_source + + @batch_source.setter + def batch_source(self, batch_source: DataSource): + """ + Sets the batch source of this feature table + """ + self._batch_source = batch_source + + @property + def stream_source(self): + """ + Returns the stream source of this feature table + """ + return self._stream_source + + @stream_source.setter + def stream_source(self, stream_source: DataSource): + """ + Sets the stream source of this feature table + """ + self._stream_source = stream_source + + @property + def max_age(self): + """ + Returns the maximum age of this feature table. This is the total maximum + amount of staleness that will be allowed during feature retrieval for + each specific feature that is looked up. + """ + return self._max_age + + @max_age.setter + def max_age(self, max_age): + """ + Set the maximum age for this feature table + """ + self._max_age = max_age + + @property + def labels(self): + """ + Returns the labels of this feature table. This is the user defined metadata + defined as a dictionary. + """ + return self._labels + + @labels.setter + def labels(self, labels: MutableMapping[str, str]): + """ + Set the labels for this feature table + """ + self._labels = labels + + @property + def created_timestamp(self): + """ + Returns the created_timestamp of this feature table + """ + return self._created_timestamp + + @property + def last_updated_timestamp(self): + """ + Returns the last_updated_timestamp of this feature table + """ + return self._last_updated_timestamp + + def is_valid(self): + """ + Validates the state of a feature table locally. Raises an exception + if feature table is invalid. + """ + + if not self.name: + raise ValueError("No name found in feature table.") + + if not self.entities: + raise ValueError("No entities found in feature table {self.name}.") + + @classmethod + def from_yaml(cls, yml: str): + """ + Creates a feature table from a YAML string body or a file path + + Args: + yml: Either a file path containing a yaml file or a YAML string + + Returns: + Returns a FeatureTable object based on the YAML file + """ + + return cls.from_dict(feast_yaml.yaml_loader(yml, load_single=True)) + + @classmethod + def from_dict(cls, ft_dict): + """ + Creates a feature table from a dict + + Args: + ft_dict: A dict representation of a feature table + + Returns: + Returns a FeatureTable object based on the feature table dict + """ + + feature_table_proto = json_format.ParseDict( + ft_dict, FeatureTableProto(), ignore_unknown_fields=True + ) + + return cls.from_proto(feature_table_proto) + + @classmethod + def _to_data_source(cls, data_source): + """ + Convert dict to data source. + """ + + source_type = SourceType(data_source.type).name + + if ( + source_type == "BATCH_FILE" + and data_source.file_options.file_format + and data_source.file_options.file_url + ): + data_source_options = FileOptions( + file_format=data_source.file_options.file_format, + file_url=data_source.file_options.file_url, + ) + elif source_type == "BATCH_BIGQUERY" and data_source.bigquery_options.table_ref: + data_source_options = BigQueryOptions( + table_ref=data_source.bigquery_options.table_ref, + ) + elif ( + source_type == "STREAM_KAFKA" + and data_source.kafka_options.bootstrap_servers + and data_source.kafka_options.topic + and data_source.kafka_options.class_path + ): + data_source_options = KafkaOptions( + bootstrap_servers=data_source.kafka_options.bootstrap_servers, + class_path=data_source.kafka_options.class_path, + topic=data_source.kafka_options.topic, + ) + elif ( + source_type == "STREAM_KINESIS" + and data_source.kinesis_options.class_path + and data_source.kinesis_options.region + and data_source.kinesis_options.stream_name + ): + data_source_options = KinesisOptions( + class_path=data_source.kinesis_options.class_path, + region=data_source.kinesis_options.region, + stream_name=data_source.kinesis_options.stream_name, + ) + else: + raise ValueError("Could not identify the source type being added") + + data_source_proto = DataSource( + type=data_source.type, + field_mapping=data_source.field_mapping, + options=data_source_options, + timestamp_column=data_source.timestamp_column, + date_partition_column=data_source.date_partition_column, + ).to_proto() + + return data_source_proto + + @classmethod + def from_proto(cls, feature_table_proto: FeatureTableProto): + """ + Creates a feature table from a protobuf representation of a feature table + + Args: + feature_table_proto: A protobuf representation of a feature table + + Returns: + Returns a FeatureTableProto object based on the feature table protobuf + """ + + feature_table = cls( + name=feature_table_proto.spec.name, + entities=[entity for entity in feature_table_proto.spec.entities], + features=[ + FeatureV2.from_proto(feature).to_proto() + for feature in feature_table_proto.spec.features + ], + labels=feature_table_proto.spec.labels, + max_age=( + None + if feature_table_proto.spec.max_age.seconds == 0 + and feature_table_proto.spec.max_age.nanos == 0 + else feature_table_proto.spec.max_age + ), + batch_source=( + None + if not feature_table_proto.spec.batch_source.ByteSize() + else cls._to_data_source(feature_table_proto.spec.batch_source) + ), + stream_source=( + None + if not feature_table_proto.spec.stream_source.ByteSize() + else cls._to_data_source(feature_table_proto.spec.stream_source) + ), + ) + + feature_table._created_timestamp = feature_table_proto.meta.created_timestamp + + return feature_table + + def to_proto(self) -> FeatureTableProto: + """ + Converts an feature table object to its protobuf representation + + Returns: + FeatureTableProto protobuf + """ + + meta = FeatureTableMetaProto( + created_timestamp=self.created_timestamp, + last_updated_timestamp=self.last_updated_timestamp, + ) + + spec = FeatureTableSpecProto( + name=self.name, + entities=self.entities, + features=self.features, + labels=self.labels, + max_age=self.max_age, + batch_source=self.batch_source, + stream_source=self.stream_source, + ) + + return FeatureTableProto(spec=spec, meta=meta) + + def to_spec_proto(self) -> FeatureTableSpecProto: + """ + Converts an FeatureTableProto object to its protobuf representation. + Used when passing FeatureTableSpecProto object to Feast request. + + Returns: + FeatureTableSpecProto protobuf + """ + + spec = FeatureTableSpecProto( + name=self.name, + entities=self.entities, + features=self.features, + labels=self.labels, + max_age=self.max_age, + batch_source=self.batch_source, + stream_source=self.stream_source, + ) + + return spec + + def to_dict(self) -> Dict: + """ + Converts feature table to dict + + :return: Dictionary object representation of feature table + """ + feature_table_dict = MessageToDict(self.to_proto()) + + # Remove meta when empty for more readable exports + if feature_table_dict["meta"] == {}: + del feature_table_dict["meta"] + + return feature_table_dict + + def to_yaml(self): + """ + Converts a feature table to a YAML string. + + :return: Feature table string returned in YAML format + """ + feature_table_dict = self.to_dict() + return yaml.dump(feature_table_dict, allow_unicode=True, sort_keys=False) + + def _update_from_feature_table(self, feature_table): + """ + Deep replaces one feature table with another + + Args: + feature_table: Feature set to use as a source of configuration + """ + + self.name = feature_table.name + self.entities = feature_table.entities + self.features = feature_table.features + self.labels = feature_table.labels + self.max_age = feature_table.max_age + self.batch_source = feature_table.batch_source + self.stream_source = feature_table.stream_source + self._created_timestamp = feature_table.created_timestamp + self._last_updated_timestamp = feature_table.last_updated_timestamp diff --git a/sdk/python/feast/feature_v2.py b/sdk/python/feast/feature_v2.py new file mode 100644 index 00000000000..f3aecf3a4fa --- /dev/null +++ b/sdk/python/feast/feature_v2.py @@ -0,0 +1,94 @@ +# 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. + +from typing import MutableMapping, Optional + +from feast.core.Feature_pb2 import FeatureSpecV2 as FeatureSpecProto +from feast.types import Value_pb2 as ValueTypeProto +from feast.value_type import ValueType + + +class FeatureV2: + """FeatureV2 field type""" + + def __init__( + self, + name: str, + dtype: ValueType, + labels: Optional[MutableMapping[str, str]] = None, + ): + self._name = name + if not isinstance(dtype, ValueType): + raise ValueError("dtype is not a valid ValueType") + self._dtype = dtype + if labels is None: + self._labels = dict() # type: MutableMapping + else: + self._labels = labels + + def __eq__(self, other): + if ( + self.name != other.name + or self.dtype != other.dtype + or self.labels != other.labels + ): + return False + return True + + @property + def name(self): + """ + Getter for name of this field + """ + return self._name + + @property + def dtype(self) -> ValueType: + """ + Getter for data type of this field + """ + return self._dtype + + @property + def labels(self) -> MutableMapping[str, str]: + """ + Getter for labels of this field + """ + return self._labels + + def to_proto(self) -> FeatureSpecProto: + """Converts FeatureV2 object to its Protocol Buffer representation""" + value_type = ValueTypeProto.ValueType.Enum.Value(self.dtype.name) + + return FeatureSpecProto( + name=self.name, value_type=value_type, labels=self.labels, + ) + + @classmethod + def from_proto(cls, feature_proto: FeatureSpecProto): + """ + Args: + feature_proto: FeatureSpecV2 protobuf object + + Returns: + FeatureV2 object + """ + + feature = cls( + name=feature_proto.name, + dtype=ValueType(feature_proto.value_type), + labels=feature_proto.labels, + ) + + return feature diff --git a/sdk/python/tests/test_feature_table.py b/sdk/python/tests/test_feature_table.py new file mode 100644 index 00000000000..a7a8849c76b --- /dev/null +++ b/sdk/python/tests/test_feature_table.py @@ -0,0 +1,101 @@ +# 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 socket +from concurrent import futures +from contextlib import closing + +import grpc +import pytest + +from feast.client import Client +from feast.core import CoreService_pb2_grpc as Core +from feast.data_source import DataSource, FileOptions, KafkaOptions, SourceType +from feast.feature_table import FeatureTable +from feast.feature_v2 import FeatureV2 +from feast.value_type import ValueType +from feast_core_server import CoreServicer + + +def find_free_port(): + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: + s.bind(("", 0)) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + return s.getsockname()[1] + + +free_port = find_free_port() + + +class TestFeatureTable: + @pytest.fixture(scope="function") + def server(self): + server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) + Core.add_CoreServiceServicer_to_server(CoreServicer(), server) + server.add_insecure_port(f"[::]:{free_port}") + server.start() + yield server + server.stop(0) + + @pytest.fixture + def client(self, server): + return Client(core_url=f"localhost:{free_port}") + + def test_feature_table_import_export_yaml(self): + + batch_source = DataSource( + type=SourceType(1).name, + field_mapping={ + "ride_distance": "ride_distance", + "ride_duration": "ride_duration", + }, + options=FileOptions(file_format="avro", file_url="data/test.avro"), + timestamp_column="ts_col", + date_partition_column="date_partition_col", + ) + + stream_source = DataSource( + type=SourceType(3).name, + field_mapping={ + "ride_distance": "ride_distance", + "ride_duration": "ride_duration", + }, + options=KafkaOptions( + bootstrap_servers="localhost:9094", + class_path="random/path/to/class", + topic="test_topic", + ), + timestamp_column="ts_col", + ) + + test_feature_table = FeatureTable( + name="car_driver", + features=[ + FeatureV2(name="ride_distance", dtype=ValueType.FLOAT).to_proto(), + FeatureV2(name="ride_duration", dtype=ValueType.STRING).to_proto(), + ], + entities=["car_driver_entity"], + labels={"team": "matchmaking"}, + batch_source=batch_source.to_proto(), + stream_source=stream_source.to_proto(), + ) + + # Create a string YAML representation of the feature table + string_yaml = test_feature_table.to_yaml() + + # Create a new feature table object from the YAML string + actual_feature_table_from_string = FeatureTable.from_yaml(string_yaml) + + # Ensure equality is upheld to original feature table + assert test_feature_table == actual_feature_table_from_string From 8f373d75c4ba724ed145d919013027daf4997006 Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Sun, 6 Sep 2020 16:34:38 +0800 Subject: [PATCH 28/36] Add comments to document to core's application.yml Signed-off-by: Zhu Zhanyan --- core/src/main/resources/application.yml | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/core/src/main/resources/application.yml b/core/src/main/resources/application.yml index 1c7b14f415d..5a26443cd5c 100644 --- a/core/src/main/resources/application.yml +++ b/core/src/main/resources/application.yml @@ -20,29 +20,37 @@ 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 + # See also for docs on options: https://api.docs.feast.dev/grpc/feast.core.pb.html#KafkaSourceConfig options: topic: feast-features bootstrapServers: localhost:9092 replicationFactor: 1 partitions: 1 - specsOptions: - specsTopic: feast-specs - specsAckTopic: feast-specs-ack - notifyIntervalMilliseconds: 1000 security: authentication: + # Enables authentication. Authentication is optional if only authentication is enabled. enabled: false + # Authentication provider type. Currently only supports 'jwt' provider. provider: jwt + # Authentication provider options. options: + # Endpoint URL to retrieve the JWK used to verify the JWT use for authentication. jwkEndpointURI: "https://www.googleapis.com/oauth2/v3/certs" + # Name of JWT claim to extract subject from JWT. subjectClaim: email authorization: + # Enables Authorization. Reqiures and forces authentication. enabled: false + # Authorization provider. Currently only 'http' provider is supported. provider: http + # Authorization provider optiosn. options: + # External authorization service endpoint. + # Feast delegates authorization this service by making a check access requests. + # See https://github.com/feast-dev/feast/blob/master/common/src/main/resources/api.yaml + # For external service's API contract. authorizationUrl: http://localhost:8082 # If set to true, HTTP REST endpoints at /api/v1 implemented by @@ -70,6 +78,7 @@ grpc: server: # The port that Feast Core gRPC service listens on port: 6565 + # Configure TLS support for gRPC service. security: enabled: false certificateChain: server.crt @@ -86,7 +95,9 @@ spring: hibernate.ddl-auto: validate datasource: driverClassName: org.postgresql.Driver + # Postgres SQL DB connection string. url: jdbc:postgresql://${DB_HOST:127.0.0.1}:${DB_PORT:5432}/${DB_DATABASE:postgres} + # Postgres SQL DB connection credentials. username: ${DB_USERNAME:postgres} password: ${DB_PASSWORD:password} flyway: @@ -98,6 +109,8 @@ management: simple: enabled: false statsd: + # Enables statsd backend to write metrics. enabled: true + # Host and port of the statsd instance. host: ${STATSD_HOST:localhost} port: ${STATSD_PORT:8125} From 170a5e831835899225fe614188aa5aafa47d49b6 Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Sun, 6 Sep 2020 16:36:52 +0800 Subject: [PATCH 29/36] Add comments to document job controller's application.yml Signed-off-by: Zhu Zhanyan --- .../src/main/resources/application.yml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/job-controller/src/main/resources/application.yml b/job-controller/src/main/resources/application.yml index 5ddbb1e97d3..b829db9337a 100644 --- a/job-controller/src/main/resources/application.yml +++ b/job-controller/src/main/resources/application.yml @@ -16,6 +16,8 @@ # feast: + # GRPC service address for Feast Core + # Feast Job Controller requires connection to Feast Core to retrieve and reload Feast metadata. core-host: localhost core-port: 6565 @@ -36,15 +38,18 @@ feast: 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 + # Direct runner options. See also for docs on options: + # https://api.docs.feast.dev/grpc/feast.core.pb.html#DirectRunnerConfigOptions options: tempLocation: gs://bucket/tempLocation - name: dataflow type: DataflowRunner + # Dataflow runner options. See also for docs on options: + # https://api.docs.feast.dev/grpc/feast.core.pb.html#DataflowRunnerConfigOptions options: project: my_gcp_project region: asia-east1 @@ -105,9 +110,14 @@ feast: bootstrapServers: localhost:9092 replicationFactor: 1 partitions: 1 + # Feast Job Controller uses Kafka to push FeatureSets to Ingestion Jobs. + # FeatureSet push options. specsOptions: + # Kafka topic to use to push FeatureSets. specsTopic: feast-specs + # Kafka topic to use to obtain acknowledgment on FeatureSets push. specsAckTopic: feast-specs-ack + # Time interval between pushing FeatureSets. notifyIntervalMilliseconds: 1000 logging: @@ -141,11 +151,13 @@ management: simple: enabled: false statsd: + # Enables statsd backend to write metrics. enabled: true + # Host and port of the statsd instance. host: ${STATSD_HOST:localhost} port: ${STATSD_PORT:8125} server: # The port number on which the Tomcat webserver that serves REST API endpoints should listen # Set default value avoiding conflicts with core & serving - port: ${SERVER_PORT:8082} \ No newline at end of file + port: ${SERVER_PORT:8082} From 73dce3a955d651b1091c2c5c578d0f66833fdc2a Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Sun, 6 Sep 2020 16:41:34 +0800 Subject: [PATCH 30/36] Add documentation on serving's application.yml. Signed-off-by: Zhu Zhanyan --- core/src/main/resources/application.yml | 2 +- serving/src/main/resources/application.yml | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/core/src/main/resources/application.yml b/core/src/main/resources/application.yml index 5a26443cd5c..e6c43fe4539 100644 --- a/core/src/main/resources/application.yml +++ b/core/src/main/resources/application.yml @@ -78,7 +78,7 @@ grpc: server: # The port that Feast Core gRPC service listens on port: 6565 - # Configure TLS support for gRPC service. + # Configure TLS transport secuirty for gRPC service. security: enabled: false certificateChain: server.crt diff --git a/serving/src/main/resources/application.yml b/serving/src/main/resources/application.yml index 734f7b00963..67b8e0749ec 100644 --- a/serving/src/main/resources/application.yml +++ b/serving/src/main/resources/application.yml @@ -1,6 +1,6 @@ feast: # GRPC service address for Feast Core - # Feast Serving requires connection to Feast Core to retrieve and reload Feast metadata (e.g. FeatureSpecs, Store information) + # Feast Serving requires connection to Feast Core to retrieve and reload Feast metadata. core-host: ${FEAST_CORE_HOST:localhost} core-grpc-port: ${FEAST_CORE_GRPC_PORT:6565} @@ -24,17 +24,29 @@ feast: security: authentication: + # Enables authentication. Authentication is optional if only authentication is enabled. enabled: false + # Authentication provider type. Currently only supports 'jwt' provider. provider: jwt + # Authentication provider options. options: + # Endpoint URL to retrieve the JWK used to verify the JWT use for authentication. jwkEndpointURI: "https://www.googleapis.com/oauth2/v3/certs" + # Name of JWT claim to extract subject from JWT. subjectClaim: email authorization: + # Enables Authorization. Reqiures and forces authentication. enabled: false + # Authorization provider. Currently only 'http' provider is supported. provider: http + # Authorization provider optiosn. options: - basePath: http://localhost:3000 + # External authorization service endpoint. + # Feast delegates authorization this service by making a check access requests. + # See https://github.com/feast-dev/feast/blob/master/common/src/main/resources/api.yaml + # For external service's API contract. + authorizationUrl: http://localhost:8082 # List of store configurations stores: @@ -121,6 +133,7 @@ grpc: # It is set default to 6566 so it does not conflict with the GRPC server on Feast Core # which defaults to port 6565 port: ${GRPC_PORT:6566} + # Configure TLS transport secuirty for gRPC service. security: enabled: false certificateChain: server.crt From 6a69d752a7c8d5673c12d1ea70e5a6c6ba3f48cb Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Sun, 6 Sep 2020 16:43:19 +0800 Subject: [PATCH 31/36] Remove FeatureStreamOptions from Core's FeastProperties as not being used. Signed-off-by: Zhu Zhanyan --- .../feast/core/config/FeastProperties.java | 56 ------------------ .../core/config/FeatureStreamConfig.java | 58 ------------------- core/src/main/resources/application.yml | 11 ---- 3 files changed, 125 deletions(-) delete mode 100644 core/src/main/java/feast/core/config/FeatureStreamConfig.java diff --git a/core/src/main/java/feast/core/config/FeastProperties.java b/core/src/main/java/feast/core/config/FeastProperties.java index fd926ea5da4..90a926628ec 100644 --- a/core/src/main/java/feast/core/config/FeastProperties.java +++ b/core/src/main/java/feast/core/config/FeastProperties.java @@ -20,8 +20,6 @@ import feast.common.auth.config.SecurityProperties.AuthenticationProperties; import feast.common.auth.config.SecurityProperties.AuthorizationProperties; import feast.common.logging.config.LoggingProperties; -import feast.common.validators.OneOfStrings; -import feast.core.config.FeastProperties.StreamProperties.FeatureStreamOptions; import java.util.Set; import javax.annotation.PostConstruct; import javax.validation.ConstraintViolation; @@ -31,7 +29,6 @@ import javax.validation.ValidatorFactory; 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; @@ -64,10 +61,6 @@ public FeastProperties() {} /* Feast Core Build Version */ @NotBlank private String version = "unknown"; - @NotNull - /* Feast Kafka stream properties */ - private StreamProperties stream; - @NotNull private SecurityProperties security; @Bean @@ -83,41 +76,6 @@ LoggingProperties loggingProperties() { return getLogging(); } - /** 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; - - /* 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; - } - } - /** * Validates all FeastProperties. This method runs after properties have been initialized and * individually and conditionally validates each class. @@ -133,20 +91,6 @@ public void validate() { 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 AuthenticationProperties Set> authenticationPropsViolations = validator.validate(getSecurity().getAuthentication()); diff --git a/core/src/main/java/feast/core/config/FeatureStreamConfig.java b/core/src/main/java/feast/core/config/FeatureStreamConfig.java deleted file mode 100644 index cc5e707964f..00000000000 --- a/core/src/main/java/feast/core/config/FeatureStreamConfig.java +++ /dev/null @@ -1,58 +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.config; - -import feast.core.config.FeastProperties.StreamProperties; -import feast.core.model.Source; -import feast.proto.core.SourceProto; -import feast.proto.core.SourceProto.KafkaSourceConfig; -import feast.proto.core.SourceProto.SourceType; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Slf4j -@Configuration -public class FeatureStreamConfig { - - @Autowired - @Bean - public Source getDefaultSource(FeastProperties feastProperties) { - StreamProperties streamProperties = feastProperties.getStream(); - SourceType featureStreamType = SourceType.valueOf(streamProperties.getType().toUpperCase()); - switch (featureStreamType) { - case KAFKA: - String bootstrapServers = streamProperties.getOptions().getBootstrapServers(); - String topicName = streamProperties.getOptions().getTopic(); - - KafkaSourceConfig sourceConfig = - KafkaSourceConfig.newBuilder() - .setBootstrapServers(bootstrapServers) - .setTopic(topicName) - .build(); - SourceProto.Source source = - SourceProto.Source.newBuilder() - .setType(featureStreamType) - .setKafkaSourceConfig(sourceConfig) - .build(); - return Source.fromProto(source, true); - default: - throw new RuntimeException("Unsupported source stream, only [KAFKA] is supported"); - } - } -} diff --git a/core/src/main/resources/application.yml b/core/src/main/resources/application.yml index e6c43fe4539..030297ebee8 100644 --- a/core/src/main/resources/application.yml +++ b/core/src/main/resources/application.yml @@ -16,17 +16,6 @@ # feast: - stream: - # Feature stream type. Only kafka is supported. - type: kafka - # Feature stream options. - # See also for docs on options: https://api.docs.feast.dev/grpc/feast.core.pb.html#KafkaSourceConfig - options: - topic: feast-features - bootstrapServers: localhost:9092 - replicationFactor: 1 - partitions: 1 - security: authentication: # Enables authentication. Authentication is optional if only authentication is enabled. From 5d934d13f79f6a114fdfc49d0f426d26a8a678b5 Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Sun, 6 Sep 2020 17:49:07 +0800 Subject: [PATCH 32/36] Update helm documentaion with helm-docs Signed-off-by: Zhu Zhanyan --- infra/charts/feast/README.md | 58 +++++++++---------- .../charts/feast/charts/feast-core/README.md | 12 ++-- .../charts/feast-jobcontroller/README.md | 20 +++---- .../feast/charts/feast-jupyter/README.md | 12 ++-- .../feast/charts/feast-serving/README.md | 12 ++-- 5 files changed, 49 insertions(+), 65 deletions(-) diff --git a/infra/charts/feast/README.md b/infra/charts/feast/README.md index 703e5779d37..9c3cb26d9e1 100644 --- a/infra/charts/feast/README.md +++ b/infra/charts/feast/README.md @@ -1,7 +1,6 @@ -feast -===== +# feast -Feature store for machine learning. Current chart version is `0.7-SNAPSHOT` +Feature store for machine learning. Current chart version is `0.8-SNAPSHOT` ## TL;DR; @@ -21,21 +20,21 @@ helm install --name myrelease feast-charts/feast \ ``` ## Introduction -This chart install Feast deployment on a Kubernetes cluster using the [Helm](https://v2.helm.sh/docs/using_helm/#installing-helm) package manager. +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 -## Chart Requirements +## Requirements | Repository | Name | Version | |------------|------|---------| -| | feast-core | 0.7-SNAPSHOT | -| | feast-jupyter | 0.7-SNAPSHOT | -| | feast-serving | 0.7-SNAPSHOT | -| | feast-serving | 0.7-SNAPSHOT | +| | feast-core | 0.8-SNAPSHOT | +| | feast-jupyter | 0.8-SNAPSHOT | +| | feast-serving | 0.8-SNAPSHOT | +| | feast-serving | 0.8-SNAPSHOT | | | 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 | @@ -43,12 +42,13 @@ This chart install Feast deployment on a Kubernetes cluster using the [Helm](htt | https://kubernetes-charts.storage.googleapis.com/ | prometheus | 11.0.2 | | https://kubernetes-charts.storage.googleapis.com/ | redis | 10.5.6 | -## Chart Values +## Values | 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-jobcontroller.enabled | bool | `true` | Flag to install Feast Job Controller | | feast-jupyter.enabled | bool | `true` | Flag to install Feast Jupyter Notebook with SDK | | feast-online-serving.enabled | bool | `true` | Flag to install Feast Online Serving | | grafana.enabled | bool | `true` | Flag to install Grafana | @@ -61,8 +61,8 @@ This chart install Feast deployment on a Kubernetes cluster using the [Helm](htt ## 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. +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 @@ -91,11 +91,11 @@ PASSED: myrelease-test-topic-create-consume-produce kubectl logs myrelease-feast-online-serving-test ``` -> The test pods can be safely deleted after the test finishes. +> 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 metrics Feast default installation includes Grafana, StatsD exporter and Prometheus. Request metrics from Feast Core and Feast Serving, as well as ingestion statistic from @@ -115,7 +115,7 @@ Visit http://localhost:9090 to access the Prometheus server: 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` +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 grant these specific roles, versus `editor` @@ -151,7 +151,7 @@ feast-core: feast-batch-serving: enabled: true gcpServiceAccount: - enabled: true + enabled: true application-override.yaml: feast: active_store: historical @@ -174,8 +174,8 @@ 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). +> 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 @@ -183,7 +183,7 @@ postgresql: 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 +kubectl get pods # Batch Serving is installed so `helm test` will also test for batch retrieval helm test myrelease @@ -194,7 +194,7 @@ helm test myrelease 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. +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 @@ -203,10 +203,10 @@ workers. Feast supports [DataflowRunner](https://beam.apache.org/documentation/r 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`. +`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 +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 @@ -216,7 +216,7 @@ gcloud compute addresses create \ --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), +Use the following Helm values to enable DataflowRuner (and Batch Serving), replacing the `<*load_balancer_ip*>` tags with the ip addresses reserved above: ```yaml @@ -256,7 +256,7 @@ feast-online-serving: feast: stores: - name: online - type: REDIS + type: REDIS config: host: port: 6379 @@ -268,7 +268,7 @@ feast-online-serving: feast-batch-serving: enabled: true gcpServiceAccount: - enabled: true + enabled: true application-override.yaml: feast: active_store: historical @@ -333,14 +333,14 @@ prometheus-statsd-exporter: - 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 +kubectl get pods # Test the installation helm test myrelease @@ -358,7 +358,7 @@ running features ingestion: https://console.cloud.google.com/dataflow 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) +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 @@ -377,7 +377,7 @@ feast-online-serving: 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), +highly available Redis via [Redis cluster](https://redis.io/topics/cluster-tutorial), sentinel or additional proxies. ### Documentation development diff --git a/infra/charts/feast/charts/feast-core/README.md b/infra/charts/feast/charts/feast-core/README.md index 2802cb5b791..e8e0e4b05e8 100644 --- a/infra/charts/feast/charts/feast-core/README.md +++ b/infra/charts/feast/charts/feast-core/README.md @@ -1,14 +1,10 @@ -feast-core -========== -Feast Core registers feature specifications. - -Current chart version is `0.8-SNAPSHOT` - - +# feast-core +![Version: 0.8-SNAPSHOT](https://img.shields.io/badge/Version-0.8-SNAPSHOT-informational?style=flat-square) +Feast Core registers feature specifications. -## Chart Values +## Values | Key | Type | Default | Description | |-----|------|---------|-------------| diff --git a/infra/charts/feast/charts/feast-jobcontroller/README.md b/infra/charts/feast/charts/feast-jobcontroller/README.md index a10fcae8861..c37cbc829f8 100644 --- a/infra/charts/feast/charts/feast-jobcontroller/README.md +++ b/infra/charts/feast/charts/feast-jobcontroller/README.md @@ -1,14 +1,10 @@ -feast-jobcontroller -========== -Feast Job Controller manage ingestion jobs. +# feast-jobcontroller -Current chart version is `0.8-SNAPSHOT` +![Version: 0.8-SNAPSHOT](https://img.shields.io/badge/Version-0.8-SNAPSHOT-informational?style=flat-square) +Feast Job Coontroller manage ingestion jobs. - - - -## Chart Values +## Values | Key | Type | Default | Description | |-----|------|---------|-------------| @@ -42,7 +38,7 @@ Current chart version is `0.8-SNAPSHOT` | 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 | `false` | Flag to enabled the probe | +| 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 | @@ -53,7 +49,7 @@ Current chart version is `0.8-SNAPSHOT` | nodeSelector | object | `{}` | Node labels for pod assignment | | podLabels | object | `{}` | Labels to be added to Feast Job Controller pods | | postgresql.existingSecret | string | `""` | Existing secret to use for authenticating to Postgres | -| prometheus.enabled | bool | `true` | Flag to enable scraping of Feast Job Controller metrics | +| prometheus.enabled | bool | `true` | Flag to enable scraping of 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 | @@ -63,8 +59,8 @@ Current chart version is `0.8-SNAPSHOT` | 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.grpc.port | int | `6570` | Service port for GRPC requests | +| service.grpc.targetPort | int | `6570` | 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 | diff --git a/infra/charts/feast/charts/feast-jupyter/README.md b/infra/charts/feast/charts/feast-jupyter/README.md index 427d3c458a8..e6a344d74f6 100644 --- a/infra/charts/feast/charts/feast-jupyter/README.md +++ b/infra/charts/feast/charts/feast-jupyter/README.md @@ -1,14 +1,10 @@ -feast-jupyter -============= -Feast Jupyter provides a Jupyter server with pre-installed Feast SDK - -Current chart version is `0.8-SNAPSHOT` - - +# feast-jupyter +![Version: 0.8-SNAPSHOT](https://img.shields.io/badge/Version-0.8-SNAPSHOT-informational?style=flat-square) +Feast Jupyter provides a Jupyter server with pre-installed Feast SDK -## Chart Values +## Values | Key | Type | Default | Description | |-----|------|---------|-------------| diff --git a/infra/charts/feast/charts/feast-serving/README.md b/infra/charts/feast/charts/feast-serving/README.md index fa3553fb8b2..1fd630a3856 100644 --- a/infra/charts/feast/charts/feast-serving/README.md +++ b/infra/charts/feast/charts/feast-serving/README.md @@ -1,14 +1,10 @@ -feast-serving -============= -Feast Serving serves low-latency latest features and historical batch features. - -Current chart version is `0.8-SNAPSHOT` - - +# feast-serving +![Version: 0.8-SNAPSHOT](https://img.shields.io/badge/Version-0.8-SNAPSHOT-informational?style=flat-square) +Feast Serving serves low-latency latest features and historical batch features. -## Chart Values +## Values | Key | Type | Default | Description | |-----|------|---------|-------------| From 6bac9545bda8a7af649515fd14fd10d4375b91f4 Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Sun, 6 Sep 2020 21:09:09 +0800 Subject: [PATCH 33/36] Revert "Remove FeatureStreamOptions from Core's FeastProperties as not being used." This reverts commit d82d072e41ad1fee4a88fa4560936fd8db19a71c. Signed-off-by: Zhu Zhanyan --- .../feast/core/config/FeastProperties.java | 56 ++++++++++++++++++ .../core/config/FeatureStreamConfig.java | 58 +++++++++++++++++++ core/src/main/resources/application.yml | 11 ++++ 3 files changed, 125 insertions(+) create mode 100644 core/src/main/java/feast/core/config/FeatureStreamConfig.java diff --git a/core/src/main/java/feast/core/config/FeastProperties.java b/core/src/main/java/feast/core/config/FeastProperties.java index 90a926628ec..fd926ea5da4 100644 --- a/core/src/main/java/feast/core/config/FeastProperties.java +++ b/core/src/main/java/feast/core/config/FeastProperties.java @@ -20,6 +20,8 @@ import feast.common.auth.config.SecurityProperties.AuthenticationProperties; import feast.common.auth.config.SecurityProperties.AuthorizationProperties; import feast.common.logging.config.LoggingProperties; +import feast.common.validators.OneOfStrings; +import feast.core.config.FeastProperties.StreamProperties.FeatureStreamOptions; import java.util.Set; import javax.annotation.PostConstruct; import javax.validation.ConstraintViolation; @@ -29,6 +31,7 @@ import javax.validation.ValidatorFactory; 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; @@ -61,6 +64,10 @@ public FeastProperties() {} /* Feast Core Build Version */ @NotBlank private String version = "unknown"; + @NotNull + /* Feast Kafka stream properties */ + private StreamProperties stream; + @NotNull private SecurityProperties security; @Bean @@ -76,6 +83,41 @@ LoggingProperties loggingProperties() { return getLogging(); } + /** 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; + + /* 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; + } + } + /** * Validates all FeastProperties. This method runs after properties have been initialized and * individually and conditionally validates each class. @@ -91,6 +133,20 @@ public void validate() { 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 AuthenticationProperties Set> authenticationPropsViolations = validator.validate(getSecurity().getAuthentication()); diff --git a/core/src/main/java/feast/core/config/FeatureStreamConfig.java b/core/src/main/java/feast/core/config/FeatureStreamConfig.java new file mode 100644 index 00000000000..cc5e707964f --- /dev/null +++ b/core/src/main/java/feast/core/config/FeatureStreamConfig.java @@ -0,0 +1,58 @@ +/* + * 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.config; + +import feast.core.config.FeastProperties.StreamProperties; +import feast.core.model.Source; +import feast.proto.core.SourceProto; +import feast.proto.core.SourceProto.KafkaSourceConfig; +import feast.proto.core.SourceProto.SourceType; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Slf4j +@Configuration +public class FeatureStreamConfig { + + @Autowired + @Bean + public Source getDefaultSource(FeastProperties feastProperties) { + StreamProperties streamProperties = feastProperties.getStream(); + SourceType featureStreamType = SourceType.valueOf(streamProperties.getType().toUpperCase()); + switch (featureStreamType) { + case KAFKA: + String bootstrapServers = streamProperties.getOptions().getBootstrapServers(); + String topicName = streamProperties.getOptions().getTopic(); + + KafkaSourceConfig sourceConfig = + KafkaSourceConfig.newBuilder() + .setBootstrapServers(bootstrapServers) + .setTopic(topicName) + .build(); + SourceProto.Source source = + SourceProto.Source.newBuilder() + .setType(featureStreamType) + .setKafkaSourceConfig(sourceConfig) + .build(); + return Source.fromProto(source, true); + default: + throw new RuntimeException("Unsupported source stream, only [KAFKA] is supported"); + } + } +} diff --git a/core/src/main/resources/application.yml b/core/src/main/resources/application.yml index 030297ebee8..e6c43fe4539 100644 --- a/core/src/main/resources/application.yml +++ b/core/src/main/resources/application.yml @@ -16,6 +16,17 @@ # feast: + stream: + # Feature stream type. Only kafka is supported. + type: kafka + # Feature stream options. + # See also for docs on options: https://api.docs.feast.dev/grpc/feast.core.pb.html#KafkaSourceConfig + options: + topic: feast-features + bootstrapServers: localhost:9092 + replicationFactor: 1 + partitions: 1 + security: authentication: # Enables authentication. Authentication is optional if only authentication is enabled. From 12becb0c697b15babb4dc93ccce9861b864a58ed Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Tue, 8 Sep 2020 10:18:27 +0800 Subject: [PATCH 34/36] Allow users to configure Feast Core's webserver port. Signed-off-by: Zhu Zhanyan --- core/src/main/resources/application.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/main/resources/application.yml b/core/src/main/resources/application.yml index e6c43fe4539..57337641d02 100644 --- a/core/src/main/resources/application.yml +++ b/core/src/main/resources/application.yml @@ -114,3 +114,8 @@ management: # Host and port of the statsd instance. host: ${STATSD_HOST:localhost} port: ${STATSD_PORT:8125} + +server: + # The port number on which the Tomcat webserver that serves REST API endpoints should listen on + # such as Feast Core Service REST API and metrics endpoints + port: 8080 From 828bcc6aac4b892cb384445a74ce5a957af086d4 Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Mon, 5 Oct 2020 10:02:23 +0800 Subject: [PATCH 35/36] Fix misleading metrics docs in application.yml Signed-off-by: Zhu Zhanyan --- core/src/main/resources/application.yml | 7 +++---- job-controller/src/main/resources/application.yml | 2 -- serving/src/main/resources/application.yml | 1 + 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/core/src/main/resources/application.yml b/core/src/main/resources/application.yml index 57337641d02..3b61e9ea913 100644 --- a/core/src/main/resources/application.yml +++ b/core/src/main/resources/application.yml @@ -109,13 +109,12 @@ management: simple: enabled: false statsd: - # Enables statsd backend to write metrics. enabled: true - # Host and port of the statsd instance. host: ${STATSD_HOST:localhost} port: ${STATSD_PORT:8125} server: - # The port number on which the Tomcat webserver that serves REST API endpoints should listen on - # such as Feast Core Service REST API and metrics endpoints + # The port number on which the Tomcat webserver that serves REST API endpoints should listen on. + # Endpoints include Feast Core Service REST API and /metrics endpoint. + # Core exposes its metrics on /metrics endpoint on this port. port: 8080 diff --git a/job-controller/src/main/resources/application.yml b/job-controller/src/main/resources/application.yml index b829db9337a..a5caf6bed66 100644 --- a/job-controller/src/main/resources/application.yml +++ b/job-controller/src/main/resources/application.yml @@ -151,9 +151,7 @@ management: simple: enabled: false statsd: - # Enables statsd backend to write metrics. enabled: true - # Host and port of the statsd instance. host: ${STATSD_HOST:localhost} port: ${STATSD_PORT:8125} diff --git a/serving/src/main/resources/application.yml b/serving/src/main/resources/application.yml index 67b8e0749ec..005148b04ca 100644 --- a/serving/src/main/resources/application.yml +++ b/serving/src/main/resources/application.yml @@ -143,4 +143,5 @@ server: # The port number on which the Tomcat webserver that serves REST API endpoints should listen # It is set by default to 8081 so it does not conflict with Tomcat webserver on Feast Core # if both Feast Core and Serving are running on the same machine + # Serving exposes its metrics on /metrics endpoint on this port. port: ${SERVER_PORT:8081} From 59e668d4aa82be1ea8e47a43d3ca0d047539ad0c Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Mon, 5 Oct 2020 10:11:28 +0800 Subject: [PATCH 36/36] Document that BigQuery's store's dataset_id option refers to dataset name not dataset id Signed-off-by: Zhu Zhanyan --- serving/src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serving/src/main/resources/application.yml b/serving/src/main/resources/application.yml index 005148b04ca..0b4862e18c2 100644 --- a/serving/src/main/resources/application.yml +++ b/serving/src/main/resources/application.yml @@ -75,7 +75,7 @@ feast: config: # Store specific configuration. # GCP Project project_id: my_project - # BigQuery Dataset Id + # Name of the BigQuery dataset to use 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