diff --git a/.github/workflows/master_only.yml b/.github/workflows/master_only.yml index cf058914d4e..e5ff7980b46 100644 --- a/.github/workflows/master_only.yml +++ b/.github/workflows/master_only.yml @@ -31,29 +31,32 @@ 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: | + source infra/scripts/setup-common-functions.sh # 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 + 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} 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/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/common-test/src/main/java/feast/common/it/DataGenerator.java b/common-test/src/main/java/feast/common/it/DataGenerator.java index 17da60b0cf8..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,7 +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; @@ -111,11 +120,33 @@ 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 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, @@ -152,7 +183,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() @@ -179,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 4b38886b1bd..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; @@ -35,6 +37,53 @@ 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 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( @@ -82,6 +131,24 @@ 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 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( @@ -141,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/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/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/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 59abf24fdf5..ef7218d13f5 100644 --- a/core/src/main/java/feast/core/grpc/CoreServiceImpl.java +++ b/core/src/main/java/feast/core/grpc/CoreServiceImpl.java @@ -28,11 +28,13 @@ 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; 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; @@ -95,6 +97,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 +162,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 +244,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) { @@ -198,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); @@ -304,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/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/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 c55830c8248..e516f5868c4 100644 --- a/core/src/main/java/feast/core/model/Project.java +++ b/core/src/main/java/feast/core/model/Project.java @@ -52,6 +52,20 @@ public class Project { mappedBy = "project") private Set featureSets; + @OneToMany( + cascade = CascadeType.ALL, + fetch = FetchType.EAGER, + orphanRemoval = true, + mappedBy = "project") + private Set entities; + + @OneToMany( + cascade = CascadeType.ALL, + fetch = FetchType.EAGER, + orphanRemoval = true, + mappedBy = "project") + private Set featureTables; + public Project() { super(); } @@ -59,6 +73,8 @@ public Project() { public Project(String name) { this.name = name; this.featureSets = new HashSet<>(); + this.entities = new HashSet<>(); + this.featureTables = new HashSet<>(); } public void addFeatureSet(FeatureSet featureSet) { @@ -66,6 +82,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..f570167c211 100644 --- a/core/src/main/java/feast/core/service/SpecService.java +++ b/core/src/main/java/feast/core/service/SpecService.java @@ -21,19 +21,34 @@ 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.FeatureTableRepository; 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.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; @@ -43,14 +58,18 @@ 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.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; @@ -65,23 +84,64 @@ @Service 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; @Autowired 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; } + /** + * 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 @@ -251,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) { @@ -277,6 +338,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 +431,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. * @@ -428,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; } /** @@ -478,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/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/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/application.yml b/core/src/main/resources/application.yml index 1c7b14f415d..3b61e9ea913 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 transport secuirty 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: @@ -101,3 +112,9 @@ management: enabled: true 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. + # 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/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/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/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(); 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 697ad424d3e..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,6 +71,45 @@ public static void globalSetUp(@Value("${grpc.server.port}") int port) { public void initState() { SourceProto.Source source = DataGenerator.getDefaultSource(); + EntityProto.EntitySpecV2 entitySpec1 = + DataGenerator.createEntitySpecV2( + "entity1", + "Entity 1 description", + ValueProto.ValueType.Enum.STRING, + 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")); + 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( + "entity3", + "Entity 3 description", + ValueProto.ValueType.Enum.STRING, + ImmutableMap.of("label_key2", "label_value2"))); apiClient.simpleApplyFeatureSet( DataGenerator.createFeatureSet( source, @@ -88,7 +130,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 +143,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 +156,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 +241,104 @@ 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 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 @@ -547,7 +687,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 +715,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 +726,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 +899,70 @@ 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 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 @@ -713,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/datatypes/java/README.md b/datatypes/java/README.md index f87a50f1bd1..ce43fe07132 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.8-SNAPSHOT ``` 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 00000000000..d3b359a5988 Binary files /dev/null and b/docs/.gitbook/assets/image (2) (3).png differ 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 00000000000..2442410112f Binary files /dev/null and b/docs/.gitbook/assets/image (3) (2) (2).png differ diff --git a/docs/.gitbook/assets/rsz_untitled23 (1).jpg b/docs/.gitbook/assets/rsz_untitled23 (1).jpg new file mode 100644 index 00000000000..b92ec6fed72 Binary files /dev/null and b/docs/.gitbook/assets/rsz_untitled23 (1).jpg differ diff --git a/docs/.gitbook/assets/rsz_untitled23.jpg b/docs/.gitbook/assets/rsz_untitled23.jpg new file mode 100644 index 00000000000..b92ec6fed72 Binary files /dev/null and b/docs/.gitbook/assets/rsz_untitled23.jpg differ 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 00000000000..02be233968d Binary files /dev/null and b/docs/.gitbook/assets/statistics-sources (1) (2).png differ diff --git a/docs/.gitbook/assets/untitled-23-.jpg b/docs/.gitbook/assets/untitled-23-.jpg new file mode 100644 index 00000000000..31299d475f2 Binary files /dev/null and b/docs/.gitbook/assets/untitled-23-.jpg differ diff --git a/docs/.gitbook/assets/untitled-25-1- (1).jpg b/docs/.gitbook/assets/untitled-25-1- (1).jpg new file mode 100644 index 00000000000..93d010406bd Binary files /dev/null and b/docs/.gitbook/assets/untitled-25-1- (1).jpg differ diff --git a/docs/.gitbook/assets/untitled-25-1-.jpg b/docs/.gitbook/assets/untitled-25-1-.jpg new file mode 100644 index 00000000000..93d010406bd Binary files /dev/null and b/docs/.gitbook/assets/untitled-25-1-.jpg differ diff --git a/docs/.gitbook/assets/untitled-26-1-.jpg b/docs/.gitbook/assets/untitled-26-1-.jpg new file mode 100644 index 00000000000..c4bbe15cf40 Binary files /dev/null and b/docs/.gitbook/assets/untitled-26-1-.jpg differ diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 8d6d889ebab..f0e2392f458 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -35,13 +35,16 @@ ## Administration +* [Security](administration/security.md) * [Audit Logging](administration/audit-logging.md) * [Troubleshooting](administration/troubleshooting.md) * [Upgrading Feast](administration/upgrading.md) ## Reference +* [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/administration/audit-logging.md b/docs/administration/audit-logging.md index d695292b4dc..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: @@ -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", @@ -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,30 @@ 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. -#### Configuration +### Request/Response Log Example + +```text +{ + "id": "45329ea9-0d48-46c5-b659-4604f6193711", + "service": "CoreService" + "status_code": "OK", + "identity": "105960238928959148073", + "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 +125,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/administration/security.md b/docs/administration/security.md new file mode 100644 index 00000000000..2350c76440b --- /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/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. + +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.** + 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 diff --git a/docs/contributing/development-guide.md b/docs/contributing/development-guide.md index e71c91d00a3..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.6.2-SNAPSHOT" +version: "0.8-SNAPSHOT" type: FEAST_SERVING_TYPE_ONLINE Rpc succeeded with OK status 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/getting-started/deploying-feast/docker-compose.md b/docs/getting-started/deploying-feast/docker-compose.md index 3b397236517..166a1bfffdd 100644 --- a/docs/getting-started/deploying-feast/docker-compose.md +++ b/docs/getting-started/deploying-feast/docker-compose.md @@ -2,13 +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. - -{% 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 %} +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 @@ -22,7 +18,8 @@ The Docker Compose deployment will take some time fully startup. During this tim 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 ``` @@ -32,22 +29,36 @@ 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="" %} ## 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 @@ -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-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
  • +
+
+ 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..60945df68af 100644 --- a/docs/reference/api/README.md +++ b/docs/reference/api/README.md @@ -9,3 +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 + +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/metrics-reference.md b/docs/reference/metrics-reference.md new file mode 100644 index 00000000000..6203c5e249c --- /dev/null +++ b/docs/reference/metrics-reference.md @@ -0,0 +1,178 @@ +# 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 on a 60 second window/bucket. 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_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 + 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/roadmap.md b/docs/roadmap.md index 0867adddf5b..ace85ff4103 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,32 +1,47 @@ # Roadmap -## Feast 0.7 \(Feature Release\) +## Feast 0.8 -[Discussion](https://github.com/feast-dev/feast/issues/834) +[Discussion](https://github.com/feast-dev/feast/issues/1018) -[GitHub Milestone](https://github.com/feast-dev/feast/milestone/4) +[Feast 0.8 RFC](https://docs.google.com/document/d/1snRxVb8ipWZjCiLlfkR4Oc28p7Fkv_UXjvxBFWjRBj4/edit#heading=h.yvkhw2cuvx5) ### **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. 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. 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. 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 -### **Proposals** +[Discussion](https://github.com/feast-dev/feast/issues/834) + +[GitHub Milestone](https://github.com/feast-dev/feast/milestone/4) + +### **New Functionality** + +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. Training-serving skew detection proposal +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) -## Feast 0.6 \(Feature Release\) +## Feast 0.6 [Discussion](https://github.com/feast-dev/feast/issues/767) @@ -45,7 +60,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) 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` | diff --git a/go.mod b/go.mod index 8f9d12581ad..137fea3e197 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-20201001230009-b5b87423c93b // 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..c8b8ce2c300 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,21 @@ 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/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= 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 +547,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/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/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/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 36d963dd242..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.7-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 | |-----|------|---------|-------------| @@ -23,7 +19,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/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 7c27fccedf6..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.7-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 | |-----|------|---------|-------------| @@ -23,7 +19,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 | @@ -42,7 +38,7 @@ Current chart version is `0.7-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.7-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.7-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-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/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 c35da52d824..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.7-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 | |-----|------|---------|-------------| @@ -17,5 +13,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/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 ad8862e1743..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.7-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 | |-----|------|---------|-------------| @@ -23,7 +19,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..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.6.2 + version: 0.8-SNAPSHOT - name: feast-serving repository: "" - version: 0.6.2 + version: 0.8-SNAPSHOT - name: feast-serving repository: "" - version: 0.6.2 + version: 0.8-SNAPSHOT - name: feast-jupyter repository: "" - version: 0.6.2 + 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/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/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/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/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/infra/scripts/validate-version-consistency.sh b/infra/scripts/validate-version-consistency.sh index f33cb8a56f4..1f028bf97d6 100755 --- a/infra/scripts/validate-version-consistency.sh +++ b/infra/scripts/validate-version-consistency.sh @@ -1,80 +1,135 @@ #!/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 +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)} + +# 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=$(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" + 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=$(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=$(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 + 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}" + "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" + if [ "${ACTUAL_OCCURRENCES}" -ge "${EXPECTED_OCCURRENCES}" ]; then + 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/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; diff --git a/job-controller/src/main/resources/application.yml b/job-controller/src/main/resources/application.yml index 5ddbb1e97d3..a5caf6bed66 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: @@ -148,4 +158,4 @@ management: 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} 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 diff --git a/protos/feast/core/CoreService.proto b/protos/feast/core/CoreService.proto index 59a50b823f0..a62dc56758f 100644 --- a/protos/feast/core/CoreService.proto +++ b/protos/feast/core/CoreService.proto @@ -23,7 +23,9 @@ 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/FeatureTable.proto"; import "feast/core/Store.proto"; import "feast/core/FeatureSetReference.proto"; import "feast/core/IngestionJob.proto"; @@ -35,6 +37,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 +76,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 @@ -94,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 { @@ -166,6 +206,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 +276,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. @@ -366,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 new file mode 100644 index 00000000000..ec7b2a07c96 --- /dev/null +++ b/protos/feast/core/Entity.proto @@ -0,0 +1,51 @@ +// +// * 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; +} 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/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]) } diff --git a/sdk/go/protos/feast/core/CoreService.pb.go b/sdk/go/protos/feast/core/CoreService.pb.go index f0e35be844c..53f82b9a6a4 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,16 +353,20 @@ func (x *ListFeatureSetsResponse) GetFeatureSets() []*FeatureSet { return nil } -type ListFeaturesRequest struct { +// Request for a single entity +type GetEntityRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Filter *ListFeaturesRequest_Filter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` + // 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 *ListFeaturesRequest) Reset() { - *x = ListFeaturesRequest{} +func (x *GetEntityRequest) Reset() { + *x = GetEntityRequest{} if protoimpl.UnsafeEnabled { mi := &file_feast_core_CoreService_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -370,13 +374,13 @@ func (x *ListFeaturesRequest) Reset() { } } -func (x *ListFeaturesRequest) String() string { +func (x *GetEntityRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListFeaturesRequest) ProtoMessage() {} +func (*GetEntityRequest) ProtoMessage() {} -func (x *ListFeaturesRequest) ProtoReflect() protoreflect.Message { +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)) @@ -388,28 +392,36 @@ func (x *ListFeaturesRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListFeaturesRequest.ProtoReflect.Descriptor instead. -func (*ListFeaturesRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use GetEntityRequest.ProtoReflect.Descriptor instead. +func (*GetEntityRequest) Descriptor() ([]byte, []int) { return file_feast_core_CoreService_proto_rawDescGZIP(), []int{4} } -func (x *ListFeaturesRequest) GetFilter() *ListFeaturesRequest_Filter { +func (x *GetEntityRequest) GetName() string { if x != nil { - return x.Filter + return x.Name } - return nil + return "" } -type ListFeaturesResponse struct { +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 - Features map[string]*FeatureSpec `protobuf:"bytes,1,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Entity *Entity `protobuf:"bytes,1,opt,name=entity,proto3" json:"entity,omitempty"` } -func (x *ListFeaturesResponse) Reset() { - *x = ListFeaturesResponse{} +func (x *GetEntityResponse) Reset() { + *x = GetEntityResponse{} if protoimpl.UnsafeEnabled { mi := &file_feast_core_CoreService_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -417,13 +429,13 @@ func (x *ListFeaturesResponse) Reset() { } } -func (x *ListFeaturesResponse) String() string { +func (x *GetEntityResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListFeaturesResponse) ProtoMessage() {} +func (*GetEntityResponse) ProtoMessage() {} -func (x *ListFeaturesResponse) ProtoReflect() protoreflect.Message { +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)) @@ -435,28 +447,29 @@ func (x *ListFeaturesResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListFeaturesResponse.ProtoReflect.Descriptor instead. -func (*ListFeaturesResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use GetEntityResponse.ProtoReflect.Descriptor instead. +func (*GetEntityResponse) Descriptor() ([]byte, []int) { return file_feast_core_CoreService_proto_rawDescGZIP(), []int{5} } -func (x *ListFeaturesResponse) GetFeatures() map[string]*FeatureSpec { +func (x *GetEntityResponse) GetEntity() *Entity { if x != nil { - return x.Features + return x.Entity } return nil } -type ListStoresRequest struct { +// Retrieves details for all versions of a specific entity +type ListEntitiesRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Filter *ListStoresRequest_Filter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` + Filter *ListEntitiesRequest_Filter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` } -func (x *ListStoresRequest) Reset() { - *x = ListStoresRequest{} +func (x *ListEntitiesRequest) Reset() { + *x = ListEntitiesRequest{} if protoimpl.UnsafeEnabled { mi := &file_feast_core_CoreService_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -464,13 +477,13 @@ func (x *ListStoresRequest) Reset() { } } -func (x *ListStoresRequest) String() string { +func (x *ListEntitiesRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListStoresRequest) ProtoMessage() {} +func (*ListEntitiesRequest) ProtoMessage() {} -func (x *ListStoresRequest) ProtoReflect() protoreflect.Message { +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)) @@ -482,28 +495,28 @@ func (x *ListStoresRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListStoresRequest.ProtoReflect.Descriptor instead. -func (*ListStoresRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use ListEntitiesRequest.ProtoReflect.Descriptor instead. +func (*ListEntitiesRequest) Descriptor() ([]byte, []int) { return file_feast_core_CoreService_proto_rawDescGZIP(), []int{6} } -func (x *ListStoresRequest) GetFilter() *ListStoresRequest_Filter { +func (x *ListEntitiesRequest) GetFilter() *ListEntitiesRequest_Filter { if x != nil { return x.Filter } return nil } -type ListStoresResponse struct { +type ListEntitiesResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Store []*Store `protobuf:"bytes,1,rep,name=store,proto3" json:"store,omitempty"` + Entities []*Entity `protobuf:"bytes,1,rep,name=entities,proto3" json:"entities,omitempty"` } -func (x *ListStoresResponse) Reset() { - *x = ListStoresResponse{} +func (x *ListEntitiesResponse) Reset() { + *x = ListEntitiesResponse{} if protoimpl.UnsafeEnabled { mi := &file_feast_core_CoreService_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -511,13 +524,13 @@ func (x *ListStoresResponse) Reset() { } } -func (x *ListStoresResponse) String() string { +func (x *ListEntitiesResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListStoresResponse) ProtoMessage() {} +func (*ListEntitiesResponse) ProtoMessage() {} -func (x *ListStoresResponse) ProtoReflect() protoreflect.Message { +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)) @@ -529,31 +542,28 @@ func (x *ListStoresResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListStoresResponse.ProtoReflect.Descriptor instead. -func (*ListStoresResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use ListEntitiesResponse.ProtoReflect.Descriptor instead. +func (*ListEntitiesResponse) Descriptor() ([]byte, []int) { return file_feast_core_CoreService_proto_rawDescGZIP(), []int{7} } -func (x *ListStoresResponse) GetStore() []*Store { +func (x *ListEntitiesResponse) GetEntities() []*Entity { if x != nil { - return x.Store + return x.Entities } return nil } -type ApplyFeatureSetRequest struct { +type ListFeaturesRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // Feature set version - // If project is unspecified, will default to 'default' project. - // If project specified does not exist, the project would be automatically created. - FeatureSet *FeatureSet `protobuf:"bytes,1,opt,name=feature_set,json=featureSet,proto3" json:"feature_set,omitempty"` + Filter *ListFeaturesRequest_Filter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` } -func (x *ApplyFeatureSetRequest) Reset() { - *x = ApplyFeatureSetRequest{} +func (x *ListFeaturesRequest) Reset() { + *x = ListFeaturesRequest{} if protoimpl.UnsafeEnabled { mi := &file_feast_core_CoreService_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -561,13 +571,13 @@ func (x *ApplyFeatureSetRequest) Reset() { } } -func (x *ApplyFeatureSetRequest) String() string { +func (x *ListFeaturesRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ApplyFeatureSetRequest) ProtoMessage() {} +func (*ListFeaturesRequest) ProtoMessage() {} -func (x *ApplyFeatureSetRequest) ProtoReflect() protoreflect.Message { +func (x *ListFeaturesRequest) ProtoReflect() protoreflect.Message { mi := &file_feast_core_CoreService_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -579,29 +589,28 @@ func (x *ApplyFeatureSetRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ApplyFeatureSetRequest.ProtoReflect.Descriptor instead. -func (*ApplyFeatureSetRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use ListFeaturesRequest.ProtoReflect.Descriptor instead. +func (*ListFeaturesRequest) Descriptor() ([]byte, []int) { return file_feast_core_CoreService_proto_rawDescGZIP(), []int{8} } -func (x *ApplyFeatureSetRequest) GetFeatureSet() *FeatureSet { +func (x *ListFeaturesRequest) GetFilter() *ListFeaturesRequest_Filter { if x != nil { - return x.FeatureSet + return x.Filter } return nil } -type ApplyFeatureSetResponse struct { +type ListFeaturesResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - FeatureSet *FeatureSet `protobuf:"bytes,1,opt,name=feature_set,json=featureSet,proto3" json:"feature_set,omitempty"` - Status ApplyFeatureSetResponse_Status `protobuf:"varint,2,opt,name=status,proto3,enum=feast.core.ApplyFeatureSetResponse_Status" json:"status,omitempty"` + Features map[string]*FeatureSpec `protobuf:"bytes,1,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } -func (x *ApplyFeatureSetResponse) Reset() { - *x = ApplyFeatureSetResponse{} +func (x *ListFeaturesResponse) Reset() { + *x = ListFeaturesResponse{} if protoimpl.UnsafeEnabled { mi := &file_feast_core_CoreService_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -609,13 +618,13 @@ func (x *ApplyFeatureSetResponse) Reset() { } } -func (x *ApplyFeatureSetResponse) String() string { +func (x *ListFeaturesResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ApplyFeatureSetResponse) ProtoMessage() {} +func (*ListFeaturesResponse) ProtoMessage() {} -func (x *ApplyFeatureSetResponse) ProtoReflect() protoreflect.Message { +func (x *ListFeaturesResponse) ProtoReflect() protoreflect.Message { mi := &file_feast_core_CoreService_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -627,33 +636,28 @@ func (x *ApplyFeatureSetResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ApplyFeatureSetResponse.ProtoReflect.Descriptor instead. -func (*ApplyFeatureSetResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use ListFeaturesResponse.ProtoReflect.Descriptor instead. +func (*ListFeaturesResponse) Descriptor() ([]byte, []int) { return file_feast_core_CoreService_proto_rawDescGZIP(), []int{9} } -func (x *ApplyFeatureSetResponse) GetFeatureSet() *FeatureSet { +func (x *ListFeaturesResponse) GetFeatures() map[string]*FeatureSpec { if x != nil { - return x.FeatureSet + return x.Features } return nil } -func (x *ApplyFeatureSetResponse) GetStatus() ApplyFeatureSetResponse_Status { - if x != nil { - return x.Status - } - return ApplyFeatureSetResponse_NO_CHANGE -} - -type GetFeastCoreVersionRequest struct { +type ListStoresRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + + Filter *ListStoresRequest_Filter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` } -func (x *GetFeastCoreVersionRequest) Reset() { - *x = GetFeastCoreVersionRequest{} +func (x *ListStoresRequest) Reset() { + *x = ListStoresRequest{} if protoimpl.UnsafeEnabled { mi := &file_feast_core_CoreService_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -661,13 +665,13 @@ func (x *GetFeastCoreVersionRequest) Reset() { } } -func (x *GetFeastCoreVersionRequest) String() string { +func (x *ListStoresRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetFeastCoreVersionRequest) ProtoMessage() {} +func (*ListStoresRequest) ProtoMessage() {} -func (x *GetFeastCoreVersionRequest) ProtoReflect() protoreflect.Message { +func (x *ListStoresRequest) ProtoReflect() protoreflect.Message { mi := &file_feast_core_CoreService_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -679,21 +683,28 @@ func (x *GetFeastCoreVersionRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetFeastCoreVersionRequest.ProtoReflect.Descriptor instead. -func (*GetFeastCoreVersionRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use ListStoresRequest.ProtoReflect.Descriptor instead. +func (*ListStoresRequest) Descriptor() ([]byte, []int) { return file_feast_core_CoreService_proto_rawDescGZIP(), []int{10} } -type GetFeastCoreVersionResponse struct { +func (x *ListStoresRequest) GetFilter() *ListStoresRequest_Filter { + if x != nil { + return x.Filter + } + return nil +} + +type ListStoresResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` + Store []*Store `protobuf:"bytes,1,rep,name=store,proto3" json:"store,omitempty"` } -func (x *GetFeastCoreVersionResponse) Reset() { - *x = GetFeastCoreVersionResponse{} +func (x *ListStoresResponse) Reset() { + *x = ListStoresResponse{} if protoimpl.UnsafeEnabled { mi := &file_feast_core_CoreService_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -701,13 +712,13 @@ func (x *GetFeastCoreVersionResponse) Reset() { } } -func (x *GetFeastCoreVersionResponse) String() string { +func (x *ListStoresResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetFeastCoreVersionResponse) ProtoMessage() {} +func (*ListStoresResponse) ProtoMessage() {} -func (x *GetFeastCoreVersionResponse) ProtoReflect() protoreflect.Message { +func (x *ListStoresResponse) ProtoReflect() protoreflect.Message { mi := &file_feast_core_CoreService_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -719,28 +730,32 @@ func (x *GetFeastCoreVersionResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetFeastCoreVersionResponse.ProtoReflect.Descriptor instead. -func (*GetFeastCoreVersionResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use ListStoresResponse.ProtoReflect.Descriptor instead. +func (*ListStoresResponse) Descriptor() ([]byte, []int) { return file_feast_core_CoreService_proto_rawDescGZIP(), []int{11} } -func (x *GetFeastCoreVersionResponse) GetVersion() string { +func (x *ListStoresResponse) GetStore() []*Store { if x != nil { - return x.Version + return x.Store } - return "" + return nil } -type UpdateStoreRequest struct { +type ApplyEntityRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Store *Store `protobuf:"bytes,1,opt,name=store,proto3" json:"store,omitempty"` + // 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 *UpdateStoreRequest) Reset() { - *x = UpdateStoreRequest{} +func (x *ApplyEntityRequest) Reset() { + *x = ApplyEntityRequest{} if protoimpl.UnsafeEnabled { mi := &file_feast_core_CoreService_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -748,13 +763,13 @@ func (x *UpdateStoreRequest) Reset() { } } -func (x *UpdateStoreRequest) String() string { +func (x *ApplyEntityRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*UpdateStoreRequest) ProtoMessage() {} +func (*ApplyEntityRequest) ProtoMessage() {} -func (x *UpdateStoreRequest) ProtoReflect() protoreflect.Message { +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)) @@ -766,29 +781,35 @@ func (x *UpdateStoreRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use UpdateStoreRequest.ProtoReflect.Descriptor instead. -func (*UpdateStoreRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use ApplyEntityRequest.ProtoReflect.Descriptor instead. +func (*ApplyEntityRequest) Descriptor() ([]byte, []int) { return file_feast_core_CoreService_proto_rawDescGZIP(), []int{12} } -func (x *UpdateStoreRequest) GetStore() *Store { +func (x *ApplyEntityRequest) GetSpec() *EntitySpecV2 { if x != nil { - return x.Store + return x.Spec } return nil } -type UpdateStoreResponse struct { +func (x *ApplyEntityRequest) GetProject() string { + if x != nil { + return x.Project + } + return "" +} + +type ApplyEntityResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Store *Store `protobuf:"bytes,1,opt,name=store,proto3" json:"store,omitempty"` - Status UpdateStoreResponse_Status `protobuf:"varint,2,opt,name=status,proto3,enum=feast.core.UpdateStoreResponse_Status" json:"status,omitempty"` + Entity *Entity `protobuf:"bytes,1,opt,name=entity,proto3" json:"entity,omitempty"` } -func (x *UpdateStoreResponse) Reset() { - *x = UpdateStoreResponse{} +func (x *ApplyEntityResponse) Reset() { + *x = ApplyEntityResponse{} if protoimpl.UnsafeEnabled { mi := &file_feast_core_CoreService_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -796,13 +817,13 @@ func (x *UpdateStoreResponse) Reset() { } } -func (x *UpdateStoreResponse) String() string { +func (x *ApplyEntityResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*UpdateStoreResponse) ProtoMessage() {} +func (*ApplyEntityResponse) ProtoMessage() {} -func (x *UpdateStoreResponse) ProtoReflect() protoreflect.Message { +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)) @@ -814,37 +835,31 @@ func (x *UpdateStoreResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use UpdateStoreResponse.ProtoReflect.Descriptor instead. -func (*UpdateStoreResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use ApplyEntityResponse.ProtoReflect.Descriptor instead. +func (*ApplyEntityResponse) Descriptor() ([]byte, []int) { return file_feast_core_CoreService_proto_rawDescGZIP(), []int{13} } -func (x *UpdateStoreResponse) GetStore() *Store { +func (x *ApplyEntityResponse) GetEntity() *Entity { if x != nil { - return x.Store + return x.Entity } return nil } -func (x *UpdateStoreResponse) GetStatus() UpdateStoreResponse_Status { - if x != nil { - return x.Status - } - return UpdateStoreResponse_NO_CHANGE -} - -// Request to create a project -type CreateProjectRequest struct { +type ApplyFeatureSetRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // Name of project (required) - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Feature set version + // If project is unspecified, will default to 'default' project. + // If project specified does not exist, the project would be automatically created. + FeatureSet *FeatureSet `protobuf:"bytes,1,opt,name=feature_set,json=featureSet,proto3" json:"feature_set,omitempty"` } -func (x *CreateProjectRequest) Reset() { - *x = CreateProjectRequest{} +func (x *ApplyFeatureSetRequest) Reset() { + *x = ApplyFeatureSetRequest{} if protoimpl.UnsafeEnabled { mi := &file_feast_core_CoreService_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -852,13 +867,13 @@ func (x *CreateProjectRequest) Reset() { } } -func (x *CreateProjectRequest) String() string { +func (x *ApplyFeatureSetRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*CreateProjectRequest) ProtoMessage() {} +func (*ApplyFeatureSetRequest) ProtoMessage() {} -func (x *CreateProjectRequest) ProtoReflect() protoreflect.Message { +func (x *ApplyFeatureSetRequest) ProtoReflect() protoreflect.Message { mi := &file_feast_core_CoreService_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -870,27 +885,29 @@ func (x *CreateProjectRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CreateProjectRequest.ProtoReflect.Descriptor instead. -func (*CreateProjectRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use ApplyFeatureSetRequest.ProtoReflect.Descriptor instead. +func (*ApplyFeatureSetRequest) Descriptor() ([]byte, []int) { return file_feast_core_CoreService_proto_rawDescGZIP(), []int{14} } -func (x *CreateProjectRequest) GetName() string { +func (x *ApplyFeatureSetRequest) GetFeatureSet() *FeatureSet { if x != nil { - return x.Name + return x.FeatureSet } - return "" + return nil } -// Response for creation of a project -type CreateProjectResponse struct { +type ApplyFeatureSetResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + + FeatureSet *FeatureSet `protobuf:"bytes,1,opt,name=feature_set,json=featureSet,proto3" json:"feature_set,omitempty"` + Status ApplyFeatureSetResponse_Status `protobuf:"varint,2,opt,name=status,proto3,enum=feast.core.ApplyFeatureSetResponse_Status" json:"status,omitempty"` } -func (x *CreateProjectResponse) Reset() { - *x = CreateProjectResponse{} +func (x *ApplyFeatureSetResponse) Reset() { + *x = ApplyFeatureSetResponse{} if protoimpl.UnsafeEnabled { mi := &file_feast_core_CoreService_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -898,13 +915,13 @@ func (x *CreateProjectResponse) Reset() { } } -func (x *CreateProjectResponse) String() string { +func (x *ApplyFeatureSetResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*CreateProjectResponse) ProtoMessage() {} +func (*ApplyFeatureSetResponse) ProtoMessage() {} -func (x *CreateProjectResponse) ProtoReflect() protoreflect.Message { +func (x *ApplyFeatureSetResponse) ProtoReflect() protoreflect.Message { mi := &file_feast_core_CoreService_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -916,23 +933,33 @@ func (x *CreateProjectResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CreateProjectResponse.ProtoReflect.Descriptor instead. -func (*CreateProjectResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use ApplyFeatureSetResponse.ProtoReflect.Descriptor instead. +func (*ApplyFeatureSetResponse) Descriptor() ([]byte, []int) { return file_feast_core_CoreService_proto_rawDescGZIP(), []int{15} } -// Request for the archival of a project -type ArchiveProjectRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields +func (x *ApplyFeatureSetResponse) GetFeatureSet() *FeatureSet { + if x != nil { + return x.FeatureSet + } + return nil +} - // Name of project to be archived - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +func (x *ApplyFeatureSetResponse) GetStatus() ApplyFeatureSetResponse_Status { + if x != nil { + return x.Status + } + return ApplyFeatureSetResponse_NO_CHANGE } -func (x *ArchiveProjectRequest) Reset() { - *x = ArchiveProjectRequest{} +type GetFeastCoreVersionRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetFeastCoreVersionRequest) Reset() { + *x = GetFeastCoreVersionRequest{} if protoimpl.UnsafeEnabled { mi := &file_feast_core_CoreService_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -940,13 +967,13 @@ func (x *ArchiveProjectRequest) Reset() { } } -func (x *ArchiveProjectRequest) String() string { +func (x *GetFeastCoreVersionRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ArchiveProjectRequest) ProtoMessage() {} +func (*GetFeastCoreVersionRequest) ProtoMessage() {} -func (x *ArchiveProjectRequest) ProtoReflect() protoreflect.Message { +func (x *GetFeastCoreVersionRequest) ProtoReflect() protoreflect.Message { mi := &file_feast_core_CoreService_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -958,27 +985,21 @@ func (x *ArchiveProjectRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ArchiveProjectRequest.ProtoReflect.Descriptor instead. -func (*ArchiveProjectRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use GetFeastCoreVersionRequest.ProtoReflect.Descriptor instead. +func (*GetFeastCoreVersionRequest) Descriptor() ([]byte, []int) { return file_feast_core_CoreService_proto_rawDescGZIP(), []int{16} } -func (x *ArchiveProjectRequest) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -// Response for archival of a project -type ArchiveProjectResponse struct { +type GetFeastCoreVersionResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + + Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` } -func (x *ArchiveProjectResponse) Reset() { - *x = ArchiveProjectResponse{} +func (x *GetFeastCoreVersionResponse) Reset() { + *x = GetFeastCoreVersionResponse{} if protoimpl.UnsafeEnabled { mi := &file_feast_core_CoreService_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -986,13 +1007,13 @@ func (x *ArchiveProjectResponse) Reset() { } } -func (x *ArchiveProjectResponse) String() string { +func (x *GetFeastCoreVersionResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ArchiveProjectResponse) ProtoMessage() {} +func (*GetFeastCoreVersionResponse) ProtoMessage() {} -func (x *ArchiveProjectResponse) ProtoReflect() protoreflect.Message { +func (x *GetFeastCoreVersionResponse) ProtoReflect() protoreflect.Message { mi := &file_feast_core_CoreService_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1004,20 +1025,28 @@ func (x *ArchiveProjectResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ArchiveProjectResponse.ProtoReflect.Descriptor instead. -func (*ArchiveProjectResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use GetFeastCoreVersionResponse.ProtoReflect.Descriptor instead. +func (*GetFeastCoreVersionResponse) Descriptor() ([]byte, []int) { return file_feast_core_CoreService_proto_rawDescGZIP(), []int{17} } -// Request for listing of projects -type ListProjectsRequest struct { +func (x *GetFeastCoreVersionResponse) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +type UpdateStoreRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + + Store *Store `protobuf:"bytes,1,opt,name=store,proto3" json:"store,omitempty"` } -func (x *ListProjectsRequest) Reset() { - *x = ListProjectsRequest{} +func (x *UpdateStoreRequest) Reset() { + *x = UpdateStoreRequest{} if protoimpl.UnsafeEnabled { mi := &file_feast_core_CoreService_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1025,13 +1054,13 @@ func (x *ListProjectsRequest) Reset() { } } -func (x *ListProjectsRequest) String() string { +func (x *UpdateStoreRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListProjectsRequest) ProtoMessage() {} +func (*UpdateStoreRequest) ProtoMessage() {} -func (x *ListProjectsRequest) ProtoReflect() protoreflect.Message { +func (x *UpdateStoreRequest) ProtoReflect() protoreflect.Message { mi := &file_feast_core_CoreService_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1043,23 +1072,29 @@ func (x *ListProjectsRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListProjectsRequest.ProtoReflect.Descriptor instead. -func (*ListProjectsRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use UpdateStoreRequest.ProtoReflect.Descriptor instead. +func (*UpdateStoreRequest) Descriptor() ([]byte, []int) { return file_feast_core_CoreService_proto_rawDescGZIP(), []int{18} } -// Response for listing of projects -type ListProjectsResponse struct { +func (x *UpdateStoreRequest) GetStore() *Store { + if x != nil { + return x.Store + } + return nil +} + +type UpdateStoreResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // List of project names (archived projects are filtered out) - Projects []string `protobuf:"bytes,1,rep,name=projects,proto3" json:"projects,omitempty"` + Store *Store `protobuf:"bytes,1,opt,name=store,proto3" json:"store,omitempty"` + Status UpdateStoreResponse_Status `protobuf:"varint,2,opt,name=status,proto3,enum=feast.core.UpdateStoreResponse_Status" json:"status,omitempty"` } -func (x *ListProjectsResponse) Reset() { - *x = ListProjectsResponse{} +func (x *UpdateStoreResponse) Reset() { + *x = UpdateStoreResponse{} if protoimpl.UnsafeEnabled { mi := &file_feast_core_CoreService_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1067,13 +1102,13 @@ func (x *ListProjectsResponse) Reset() { } } -func (x *ListProjectsResponse) String() string { +func (x *UpdateStoreResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListProjectsResponse) ProtoMessage() {} +func (*UpdateStoreResponse) ProtoMessage() {} -func (x *ListProjectsResponse) ProtoReflect() protoreflect.Message { +func (x *UpdateStoreResponse) ProtoReflect() protoreflect.Message { mi := &file_feast_core_CoreService_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1085,29 +1120,37 @@ func (x *ListProjectsResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListProjectsResponse.ProtoReflect.Descriptor instead. -func (*ListProjectsResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use UpdateStoreResponse.ProtoReflect.Descriptor instead. +func (*UpdateStoreResponse) Descriptor() ([]byte, []int) { return file_feast_core_CoreService_proto_rawDescGZIP(), []int{19} } -func (x *ListProjectsResponse) GetProjects() []string { +func (x *UpdateStoreResponse) GetStore() *Store { if x != nil { - return x.Projects + return x.Store } return nil } -// Request for listing ingestion jobs -type ListIngestionJobsRequest struct { +func (x *UpdateStoreResponse) GetStatus() UpdateStoreResponse_Status { + if x != nil { + return x.Status + } + return UpdateStoreResponse_NO_CHANGE +} + +// Request to create a project +type CreateProjectRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Filter *ListIngestionJobsRequest_Filter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` + // Name of project (required) + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` } -func (x *ListIngestionJobsRequest) Reset() { - *x = ListIngestionJobsRequest{} +func (x *CreateProjectRequest) Reset() { + *x = CreateProjectRequest{} if protoimpl.UnsafeEnabled { mi := &file_feast_core_CoreService_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1115,13 +1158,13 @@ func (x *ListIngestionJobsRequest) Reset() { } } -func (x *ListIngestionJobsRequest) String() string { +func (x *CreateProjectRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListIngestionJobsRequest) ProtoMessage() {} +func (*CreateProjectRequest) ProtoMessage() {} -func (x *ListIngestionJobsRequest) ProtoReflect() protoreflect.Message { +func (x *CreateProjectRequest) ProtoReflect() protoreflect.Message { mi := &file_feast_core_CoreService_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1133,29 +1176,27 @@ func (x *ListIngestionJobsRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListIngestionJobsRequest.ProtoReflect.Descriptor instead. -func (*ListIngestionJobsRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use CreateProjectRequest.ProtoReflect.Descriptor instead. +func (*CreateProjectRequest) Descriptor() ([]byte, []int) { return file_feast_core_CoreService_proto_rawDescGZIP(), []int{20} } -func (x *ListIngestionJobsRequest) GetFilter() *ListIngestionJobsRequest_Filter { +func (x *CreateProjectRequest) GetName() string { if x != nil { - return x.Filter + return x.Name } - return nil + return "" } -// Response from listing ingestion jobs -type ListIngestionJobsResponse struct { +// Response for creation of a project +type CreateProjectResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - - Jobs []*IngestionJob `protobuf:"bytes,1,rep,name=jobs,proto3" json:"jobs,omitempty"` } -func (x *ListIngestionJobsResponse) Reset() { - *x = ListIngestionJobsResponse{} +func (x *CreateProjectResponse) Reset() { + *x = CreateProjectResponse{} if protoimpl.UnsafeEnabled { mi := &file_feast_core_CoreService_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1163,13 +1204,13 @@ func (x *ListIngestionJobsResponse) Reset() { } } -func (x *ListIngestionJobsResponse) String() string { +func (x *CreateProjectResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListIngestionJobsResponse) ProtoMessage() {} +func (*CreateProjectResponse) ProtoMessage() {} -func (x *ListIngestionJobsResponse) ProtoReflect() protoreflect.Message { +func (x *CreateProjectResponse) ProtoReflect() protoreflect.Message { mi := &file_feast_core_CoreService_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1181,30 +1222,23 @@ func (x *ListIngestionJobsResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListIngestionJobsResponse.ProtoReflect.Descriptor instead. -func (*ListIngestionJobsResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use CreateProjectResponse.ProtoReflect.Descriptor instead. +func (*CreateProjectResponse) Descriptor() ([]byte, []int) { return file_feast_core_CoreService_proto_rawDescGZIP(), []int{21} } -func (x *ListIngestionJobsResponse) GetJobs() []*IngestionJob { - if x != nil { - return x.Jobs - } - return nil -} - -// Request to restart ingestion job -type RestartIngestionJobRequest struct { +// Request for the archival of a project +type ArchiveProjectRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // Job ID assigned by Feast - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // Name of project to be archived + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` } -func (x *RestartIngestionJobRequest) Reset() { - *x = RestartIngestionJobRequest{} +func (x *ArchiveProjectRequest) Reset() { + *x = ArchiveProjectRequest{} if protoimpl.UnsafeEnabled { mi := &file_feast_core_CoreService_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1212,13 +1246,13 @@ func (x *RestartIngestionJobRequest) Reset() { } } -func (x *RestartIngestionJobRequest) String() string { +func (x *ArchiveProjectRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*RestartIngestionJobRequest) ProtoMessage() {} +func (*ArchiveProjectRequest) ProtoMessage() {} -func (x *RestartIngestionJobRequest) ProtoReflect() protoreflect.Message { +func (x *ArchiveProjectRequest) ProtoReflect() protoreflect.Message { mi := &file_feast_core_CoreService_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1230,42 +1264,757 @@ func (x *RestartIngestionJobRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use RestartIngestionJobRequest.ProtoReflect.Descriptor instead. -func (*RestartIngestionJobRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use ArchiveProjectRequest.ProtoReflect.Descriptor instead. +func (*ArchiveProjectRequest) Descriptor() ([]byte, []int) { return file_feast_core_CoreService_proto_rawDescGZIP(), []int{22} } -func (x *RestartIngestionJobRequest) GetId() string { +func (x *ArchiveProjectRequest) GetName() string { if x != nil { - return x.Id + return x.Name } return "" } -// Response from restartingan injestion job -type RestartIngestionJobResponse struct { +// Response for archival of a project +type ArchiveProjectResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } -func (x *RestartIngestionJobResponse) Reset() { - *x = RestartIngestionJobResponse{} +func (x *ArchiveProjectResponse) Reset() { + *x = ArchiveProjectResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ArchiveProjectResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ArchiveProjectResponse) ProtoMessage() {} + +func (x *ArchiveProjectResponse) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ArchiveProjectResponse.ProtoReflect.Descriptor instead. +func (*ArchiveProjectResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{23} +} + +// Request for listing of projects +type ListProjectsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ListProjectsRequest) Reset() { + *x = ListProjectsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListProjectsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListProjectsRequest) ProtoMessage() {} + +func (x *ListProjectsRequest) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListProjectsRequest.ProtoReflect.Descriptor instead. +func (*ListProjectsRequest) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{24} +} + +// Response for listing of projects +type ListProjectsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // List of project names (archived projects are filtered out) + Projects []string `protobuf:"bytes,1,rep,name=projects,proto3" json:"projects,omitempty"` +} + +func (x *ListProjectsResponse) Reset() { + *x = ListProjectsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListProjectsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListProjectsResponse) ProtoMessage() {} + +func (x *ListProjectsResponse) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListProjectsResponse.ProtoReflect.Descriptor instead. +func (*ListProjectsResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{25} +} + +func (x *ListProjectsResponse) GetProjects() []string { + if x != nil { + return x.Projects + } + return nil +} + +// Request for listing ingestion jobs +type ListIngestionJobsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Filter *ListIngestionJobsRequest_Filter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` +} + +func (x *ListIngestionJobsRequest) Reset() { + *x = ListIngestionJobsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListIngestionJobsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListIngestionJobsRequest) ProtoMessage() {} + +func (x *ListIngestionJobsRequest) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[26] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListIngestionJobsRequest.ProtoReflect.Descriptor instead. +func (*ListIngestionJobsRequest) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{26} +} + +func (x *ListIngestionJobsRequest) GetFilter() *ListIngestionJobsRequest_Filter { + if x != nil { + return x.Filter + } + return nil +} + +// Response from listing ingestion jobs +type ListIngestionJobsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Jobs []*IngestionJob `protobuf:"bytes,1,rep,name=jobs,proto3" json:"jobs,omitempty"` +} + +func (x *ListIngestionJobsResponse) Reset() { + *x = ListIngestionJobsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListIngestionJobsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListIngestionJobsResponse) ProtoMessage() {} + +func (x *ListIngestionJobsResponse) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[27] + 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 ListIngestionJobsResponse.ProtoReflect.Descriptor instead. +func (*ListIngestionJobsResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{27} +} + +func (x *ListIngestionJobsResponse) GetJobs() []*IngestionJob { + if x != nil { + return x.Jobs + } + return nil +} + +// Request to restart ingestion job +type RestartIngestionJobRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Job ID assigned by Feast + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *RestartIngestionJobRequest) Reset() { + *x = RestartIngestionJobRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RestartIngestionJobRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RestartIngestionJobRequest) ProtoMessage() {} + +func (x *RestartIngestionJobRequest) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[28] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RestartIngestionJobRequest.ProtoReflect.Descriptor instead. +func (*RestartIngestionJobRequest) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{28} +} + +func (x *RestartIngestionJobRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +// Response from restartingan injestion job +type RestartIngestionJobResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *RestartIngestionJobResponse) Reset() { + *x = RestartIngestionJobResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RestartIngestionJobResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RestartIngestionJobResponse) ProtoMessage() {} + +func (x *RestartIngestionJobResponse) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[29] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RestartIngestionJobResponse.ProtoReflect.Descriptor instead. +func (*RestartIngestionJobResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{29} +} + +// Request to stop ingestion job +type StopIngestionJobRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Job ID assigned by Feast + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *StopIngestionJobRequest) Reset() { + *x = StopIngestionJobRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StopIngestionJobRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StopIngestionJobRequest) ProtoMessage() {} + +func (x *StopIngestionJobRequest) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[30] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StopIngestionJobRequest.ProtoReflect.Descriptor instead. +func (*StopIngestionJobRequest) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{30} +} + +func (x *StopIngestionJobRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +// Request from stopping an ingestion job +type StopIngestionJobResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *StopIngestionJobResponse) Reset() { + *x = StopIngestionJobResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StopIngestionJobResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StopIngestionJobResponse) ProtoMessage() {} + +func (x *StopIngestionJobResponse) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[31] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StopIngestionJobResponse.ProtoReflect.Descriptor instead. +func (*StopIngestionJobResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{31} +} + +type GetFeatureStatisticsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Feature set to retrieve the statistics for. A fully qualified feature set + // id in the format of project/feature_set must be provided. + FeatureSetId string `protobuf:"bytes,1,opt,name=feature_set_id,json=featureSetId,proto3" json:"feature_set_id,omitempty"` + // Optional filter which filters returned statistics by selected features. These + // features must be present in the data that is being processed. + Features []string `protobuf:"bytes,2,rep,name=features,proto3" json:"features,omitempty"` + // Optional filter to select store over which the statistics will retrieved. + // Only historical stores are allowed. + Store string `protobuf:"bytes,3,opt,name=store,proto3" json:"store,omitempty"` + // Optional start and end dates over which to filter statistical data + // Start date is inclusive, but end date is not. + // Only dates are supported, not times. + // Cannot be used with dataset_ids. + // If this period spans multiple days, unaggregatable statistics will be dropped. + StartDate *timestamp.Timestamp `protobuf:"bytes,4,opt,name=start_date,json=startDate,proto3" json:"start_date,omitempty"` + EndDate *timestamp.Timestamp `protobuf:"bytes,5,opt,name=end_date,json=endDate,proto3" json:"end_date,omitempty"` + // Optional list of ingestion Ids by which to filter data before + // retrieving statistics. + // Cannot be used with the date ranges + // If multiple dataset ids are provided, unaggregatable statistics will be dropped. + IngestionIds []string `protobuf:"bytes,6,rep,name=ingestion_ids,json=ingestionIds,proto3" json:"ingestion_ids,omitempty"` + // Setting this flag to true will force a recalculation of statistics and overwrite results currently in the + // cache, if any. + ForceRefresh bool `protobuf:"varint,7,opt,name=force_refresh,json=forceRefresh,proto3" json:"force_refresh,omitempty"` +} + +func (x *GetFeatureStatisticsRequest) Reset() { + *x = GetFeatureStatisticsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetFeatureStatisticsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetFeatureStatisticsRequest) ProtoMessage() {} + +func (x *GetFeatureStatisticsRequest) ProtoReflect() protoreflect.Message { + mi := &file_feast_core_CoreService_proto_msgTypes[32] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetFeatureStatisticsRequest.ProtoReflect.Descriptor instead. +func (*GetFeatureStatisticsRequest) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{32} +} + +func (x *GetFeatureStatisticsRequest) GetFeatureSetId() string { + if x != nil { + return x.FeatureSetId + } + return "" +} + +func (x *GetFeatureStatisticsRequest) GetFeatures() []string { + if x != nil { + return x.Features + } + return nil +} + +func (x *GetFeatureStatisticsRequest) GetStore() string { + if x != nil { + return x.Store + } + return "" +} + +func (x *GetFeatureStatisticsRequest) GetStartDate() *timestamp.Timestamp { + if x != nil { + return x.StartDate + } + return nil +} + +func (x *GetFeatureStatisticsRequest) GetEndDate() *timestamp.Timestamp { + if x != nil { + return x.EndDate + } + return nil +} + +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.Reference + } + return nil +} + +func (x *UpdateFeatureSetStatusRequest) GetStatus() FeatureSetStatus { + if x != nil { + return x.Status + } + return FeatureSetStatus_STATUS_INVALID +} + +type UpdateFeatureSetStatusResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *UpdateFeatureSetStatusResponse) Reset() { + *x = UpdateFeatureSetStatusResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_core_CoreService_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateFeatureSetStatusResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateFeatureSetStatusResponse) ProtoMessage() {} + +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 { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateFeatureSetStatusResponse.ProtoReflect.Descriptor instead. +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[23] + mi := &file_feast_core_CoreService_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } -func (x *RestartIngestionJobResponse) String() string { +func (x *ApplyFeatureTableResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*RestartIngestionJobResponse) ProtoMessage() {} +func (*ApplyFeatureTableResponse) ProtoMessage() {} -func (x *RestartIngestionJobResponse) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[23] +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 { @@ -1276,38 +2025,47 @@ func (x *RestartIngestionJobResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use RestartIngestionJobResponse.ProtoReflect.Descriptor instead. -func (*RestartIngestionJobResponse) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{23} +// Deprecated: Use ApplyFeatureTableResponse.ProtoReflect.Descriptor instead. +func (*ApplyFeatureTableResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{37} } -// Request to stop ingestion job -type StopIngestionJobRequest struct { +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 - // Job ID assigned by Feast - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // 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 *StopIngestionJobRequest) Reset() { - *x = StopIngestionJobRequest{} +func (x *GetFeatureTableRequest) Reset() { + *x = GetFeatureTableRequest{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[24] + mi := &file_feast_core_CoreService_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } -func (x *StopIngestionJobRequest) String() string { +func (x *GetFeatureTableRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*StopIngestionJobRequest) ProtoMessage() {} +func (*GetFeatureTableRequest) ProtoMessage() {} -func (x *StopIngestionJobRequest) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[24] +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 { @@ -1318,42 +2076,51 @@ func (x *StopIngestionJobRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use StopIngestionJobRequest.ProtoReflect.Descriptor instead. -func (*StopIngestionJobRequest) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{24} +// Deprecated: Use GetFeatureTableRequest.ProtoReflect.Descriptor instead. +func (*GetFeatureTableRequest) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{38} } -func (x *StopIngestionJobRequest) GetId() string { +func (x *GetFeatureTableRequest) GetProject() string { if x != nil { - return x.Id + return x.Project } return "" } -// Request from stopping an ingestion job -type StopIngestionJobResponse struct { +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 *StopIngestionJobResponse) Reset() { - *x = StopIngestionJobResponse{} +func (x *GetFeatureTableResponse) Reset() { + *x = GetFeatureTableResponse{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[25] + mi := &file_feast_core_CoreService_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } -func (x *StopIngestionJobResponse) String() string { +func (x *GetFeatureTableResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*StopIngestionJobResponse) ProtoMessage() {} +func (*GetFeatureTableResponse) ProtoMessage() {} -func (x *StopIngestionJobResponse) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[25] +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 { @@ -1364,59 +2131,44 @@ func (x *StopIngestionJobResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use StopIngestionJobResponse.ProtoReflect.Descriptor instead. -func (*StopIngestionJobResponse) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{25} +// Deprecated: Use GetFeatureTableResponse.ProtoReflect.Descriptor instead. +func (*GetFeatureTableResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{39} } -type GetFeatureStatisticsRequest struct { +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 - // Feature set to retrieve the statistics for. A fully qualified feature set - // id in the format of project/feature_set must be provided. - FeatureSetId string `protobuf:"bytes,1,opt,name=feature_set_id,json=featureSetId,proto3" json:"feature_set_id,omitempty"` - // Optional filter which filters returned statistics by selected features. These - // features must be present in the data that is being processed. - Features []string `protobuf:"bytes,2,rep,name=features,proto3" json:"features,omitempty"` - // Optional filter to select store over which the statistics will retrieved. - // Only historical stores are allowed. - Store string `protobuf:"bytes,3,opt,name=store,proto3" json:"store,omitempty"` - // Optional start and end dates over which to filter statistical data - // Start date is inclusive, but end date is not. - // Only dates are supported, not times. - // Cannot be used with dataset_ids. - // If this period spans multiple days, unaggregatable statistics will be dropped. - StartDate *timestamp.Timestamp `protobuf:"bytes,4,opt,name=start_date,json=startDate,proto3" json:"start_date,omitempty"` - EndDate *timestamp.Timestamp `protobuf:"bytes,5,opt,name=end_date,json=endDate,proto3" json:"end_date,omitempty"` - // Optional list of ingestion Ids by which to filter data before - // retrieving statistics. - // Cannot be used with the date ranges - // If multiple dataset ids are provided, unaggregatable statistics will be dropped. - IngestionIds []string `protobuf:"bytes,6,rep,name=ingestion_ids,json=ingestionIds,proto3" json:"ingestion_ids,omitempty"` - // Setting this flag to true will force a recalculation of statistics and overwrite results currently in the - // cache, if any. - ForceRefresh bool `protobuf:"varint,7,opt,name=force_refresh,json=forceRefresh,proto3" json:"force_refresh,omitempty"` + // Filter used when listing Feature Tables + Filter *ListFeatureTablesRequest_Filter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` } -func (x *GetFeatureStatisticsRequest) Reset() { - *x = GetFeatureStatisticsRequest{} +func (x *ListFeatureTablesRequest) Reset() { + *x = ListFeatureTablesRequest{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[26] + mi := &file_feast_core_CoreService_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } -func (x *GetFeatureStatisticsRequest) String() string { +func (x *ListFeatureTablesRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetFeatureStatisticsRequest) ProtoMessage() {} +func (*ListFeatureTablesRequest) ProtoMessage() {} -func (x *GetFeatureStatisticsRequest) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[26] +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 { @@ -1427,88 +2179,44 @@ func (x *GetFeatureStatisticsRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetFeatureStatisticsRequest.ProtoReflect.Descriptor instead. -func (*GetFeatureStatisticsRequest) Descriptor() ([]byte, []int) { - return file_feast_core_CoreService_proto_rawDescGZIP(), []int{26} -} - -func (x *GetFeatureStatisticsRequest) GetFeatureSetId() string { - if x != nil { - return x.FeatureSetId - } - return "" -} - -func (x *GetFeatureStatisticsRequest) GetFeatures() []string { - if x != nil { - return x.Features - } - return nil -} - -func (x *GetFeatureStatisticsRequest) GetStore() string { - if x != nil { - return x.Store - } - return "" -} - -func (x *GetFeatureStatisticsRequest) GetStartDate() *timestamp.Timestamp { - if x != nil { - return x.StartDate - } - return nil -} - -func (x *GetFeatureStatisticsRequest) GetEndDate() *timestamp.Timestamp { - if x != nil { - return x.EndDate - } - return nil +// Deprecated: Use ListFeatureTablesRequest.ProtoReflect.Descriptor instead. +func (*ListFeatureTablesRequest) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{40} } -func (x *GetFeatureStatisticsRequest) GetIngestionIds() []string { +func (x *ListFeatureTablesRequest) GetFilter() *ListFeatureTablesRequest_Filter { if x != nil { - return x.IngestionIds + return x.Filter } return nil } -func (x *GetFeatureStatisticsRequest) GetForceRefresh() bool { - if x != nil { - return x.ForceRefresh - } - return false -} - -type GetFeatureStatisticsResponse struct { +type ListFeatureTablesResponse 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"` + // List of matching Feature Tables + Tables []*FeatureTable `protobuf:"bytes,1,rep,name=tables,proto3" json:"tables,omitempty"` } -func (x *GetFeatureStatisticsResponse) Reset() { - *x = GetFeatureStatisticsResponse{} +func (x *ListFeatureTablesResponse) Reset() { + *x = ListFeatureTablesResponse{} if protoimpl.UnsafeEnabled { - mi := &file_feast_core_CoreService_proto_msgTypes[27] + mi := &file_feast_core_CoreService_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } -func (x *GetFeatureStatisticsResponse) String() string { +func (x *ListFeatureTablesResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetFeatureStatisticsResponse) ProtoMessage() {} +func (*ListFeatureTablesResponse) ProtoMessage() {} -func (x *GetFeatureStatisticsResponse) ProtoReflect() protoreflect.Message { - mi := &file_feast_core_CoreService_proto_msgTypes[27] +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 { @@ -1519,14 +2227,14 @@ 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} +// Deprecated: Use ListFeatureTablesResponse.ProtoReflect.Descriptor instead. +func (*ListFeatureTablesResponse) Descriptor() ([]byte, []int) { + return file_feast_core_CoreService_proto_rawDescGZIP(), []int{41} } -func (x *GetFeatureStatisticsResponse) GetDatasetFeatureStatisticsList() *v0.DatasetFeatureStatisticsList { +func (x *ListFeatureTablesResponse) GetTables() []*FeatureTable { if x != nil { - return x.DatasetFeatureStatisticsList + return x.Tables } return nil } @@ -1555,12 +2263,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[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1573,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[28] + mi := &file_feast_core_CoreService_proto_msgTypes[42] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1610,6 +2321,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[44] + 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[44] + 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 +2408,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[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1643,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[30] + mi := &file_feast_core_CoreService_proto_msgTypes[46] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1656,7 +2434,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 +2470,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[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1705,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[33] + mi := &file_feast_core_CoreService_proto_msgTypes[49] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1718,7 +2496,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 +2522,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[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1757,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[34] + mi := &file_feast_core_CoreService_proto_msgTypes[50] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1770,7 +2548,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 { @@ -1794,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{ @@ -1804,287 +2641,429 @@ 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, - 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, 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, 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, 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, - 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, 0x51, 0x0a, 0x16, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x46, 0x65, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x37, - 0x0a, 0x0b, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, - 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x0a, 0x66, 0x65, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x22, 0xd4, 0x01, 0x0a, 0x17, 0x41, 0x70, 0x70, 0x6c, - 0x79, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0b, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, - 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, - 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, - 0x52, 0x0a, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x12, 0x42, 0x0a, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x66, - 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x46, - 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x22, 0x3c, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x4f, - 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x52, 0x45, - 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, - 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x44, 0x10, 0x03, 0x22, 0x1c, - 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x73, 0x74, 0x43, 0x6f, 0x72, 0x65, 0x56, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x37, 0x0a, 0x1b, - 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x73, 0x74, 0x43, 0x6f, 0x72, 0x65, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3d, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, - 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x05, 0x73, - 0x74, 0x6f, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x66, 0x65, 0x61, - 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x05, 0x73, - 0x74, 0x6f, 0x72, 0x65, 0x22, 0xa4, 0x01, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, - 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x05, - 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x66, 0x65, - 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x05, - 0x73, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x3e, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, - 0x72, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x24, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x10, 0x00, 0x12, 0x0b, - 0x0a, 0x07, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x22, 0x2a, 0x0a, 0x14, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x17, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x2b, 0x0a, 0x15, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x50, 0x72, 0x6f, 0x6a, 0x65, - 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x18, 0x0a, - 0x16, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x15, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x50, - 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x32, - 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, - 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, - 0x74, 0x73, 0x22, 0xee, 0x01, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x67, 0x65, 0x73, - 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x43, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x2b, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x73, 0x52, 0x65, + 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, 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, + 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, 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, 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, + 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, 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, - 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, + 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, 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, 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, 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, + 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, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x49, - 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x73, 0x12, 0x24, 0x2e, 0x66, - 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, - 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x66, 0x0a, 0x13, 0x52, 0x65, 0x73, - 0x74, 0x61, 0x72, 0x74, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, - 0x12, 0x26, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, - 0x73, 0x74, 0x61, 0x72, 0x74, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, - 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, - 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x49, 0x6e, 0x67, - 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x5d, 0x0a, 0x10, 0x53, 0x74, 0x6f, 0x70, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, - 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x12, 0x23, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x63, 0x6f, - 0x72, 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x49, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, - 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x66, 0x65, 0x61, - 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x49, 0x6e, 0x67, 0x65, - 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x42, 0x59, 0x0a, 0x10, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x63, 0x6f, 0x72, 0x65, 0x42, 0x10, 0x43, 0x6f, 0x72, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x5a, 0x33, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x66, 0x65, 0x61, - 0x73, 0x74, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, - 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 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, 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 ( @@ -2100,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, 35) +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 @@ -2108,101 +3087,153 @@ 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 + (*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{ - 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 + 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 + 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 + 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() } @@ -2210,7 +3241,9 @@ 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_FeatureTable_proto_init() file_feast_core_Store_proto_init() file_feast_core_FeatureSetReference_proto_init() file_feast_core_IngestionJob_proto_init() @@ -2264,7 +3297,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 +3309,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 +3321,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 +3333,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 +3345,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 +3357,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 +3369,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 +3381,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 +3393,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 +3405,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 +3417,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 +3429,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 +3441,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 +3453,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 +3465,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 +3477,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 +3489,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 +3501,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 +3513,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 +3525,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 +3537,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 +3549,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 +3561,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 +3573,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 +3585,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 +3609,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 +3645,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 +3657,150 @@ 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.(*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: + 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.(*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: + 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.(*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 + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + 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 + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + 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 @@ -2599,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{ @@ -2606,9 +3831,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: 53, NumExtensions: 0, - NumServices: 1, + NumServices: 2, }, GoTypes: file_feast_core_CoreService_proto_goTypes, DependencyIndexes: file_feast_core_CoreService_proto_depIdxs, @@ -2637,6 +3862,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 +3895,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 @@ -2675,29 +3915,33 @@ type CoreServiceClient interface { UpdateStore(ctx context.Context, in *UpdateStoreRequest, opts ...grpc.CallOption) (*UpdateStoreResponse, error) // Creates a project. Projects serve as namespaces within which resources like features will be // created. Feature set names as must be unique within a project while field (Feature/Entity) names - // must be unique within a Feature Set. Project names themselves must be globally unique. - CreateProject(ctx context.Context, in *CreateProjectRequest, opts ...grpc.CallOption) (*CreateProjectResponse, error) - // Archives a project. Archived projects will continue to exist and function, but won't be visible - // through the Core API. Any existing ingestion or serving requests will continue to function, - // but will result in warning messages being logged. It is not possible to unarchive a project - // through the Core API - 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) + // must be unique within a Feature Set. Project names themselves must be globally unique. + CreateProject(ctx context.Context, in *CreateProjectRequest, opts ...grpc.CallOption) (*CreateProjectResponse, error) + // Archives a project. Archived projects will continue to exist and function, but won't be visible + // through the Core API. Any existing ingestion or serving requests will continue to function, + // but will result in warning messages being logged. It is not possible to unarchive a project + // through the Core API + 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) + // 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 { @@ -2726,6 +3970,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,6 +4024,24 @@ func (c *coreServiceClient) ApplyFeatureSet(ctx context.Context, in *ApplyFeatur return out, nil } +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) 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) 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...) @@ -2807,27 +4078,36 @@ func (c *coreServiceClient) ListProjects(ctx context.Context, in *ListProjectsRe return out, nil } -func (c *coreServiceClient) ListIngestionJobs(ctx context.Context, in *ListIngestionJobsRequest, opts ...grpc.CallOption) (*ListIngestionJobsResponse, error) { - out := new(ListIngestionJobsResponse) - err := c.cc.Invoke(ctx, "/feast.core.CoreService/ListIngestionJobs", in, out, opts...) +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 } 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) 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) 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) 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 } @@ -2840,6 +4120,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 +4153,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 +4182,24 @@ 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) + // 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. @@ -2913,6 +4212,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 +4230,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 +4248,17 @@ 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) UpdateFeatureSetStatus(context.Context, *UpdateFeatureSetStatusRequest) (*UpdateFeatureSetStatusResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateFeatureSetStatus not implemented") } -func (*UnimplementedCoreServiceServer) RestartIngestionJob(context.Context, *RestartIngestionJobRequest) (*RestartIngestionJobResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method RestartIngestionJob not implemented") +func (*UnimplementedCoreServiceServer) ApplyFeatureTable(context.Context, *ApplyFeatureTableRequest) (*ApplyFeatureTableResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ApplyFeatureTable not implemented") } -func (*UnimplementedCoreServiceServer) StopIngestionJob(context.Context, *StopIngestionJobRequest) (*StopIngestionJobResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method StopIngestionJob 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) { @@ -2990,6 +4301,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,6 +4409,42 @@ func _CoreService_ApplyFeatureSet_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } +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).ApplyEntity(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/feast.core.CoreService/ApplyEntity", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CoreServiceServer).ApplyEntity(ctx, req.(*ApplyEntityRequest)) + } + return interceptor(ctx, in, info, handler) +} + +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).ListEntities(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/feast.core.CoreService/ListEntities", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CoreServiceServer).ListEntities(ctx, req.(*ListEntitiesRequest)) + } + 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) if err := dec(in); err != nil { @@ -3152,56 +4517,74 @@ func _CoreService_ListProjects_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } -func _CoreService_ListIngestionJobs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ListIngestionJobsRequest) +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).ListIngestionJobs(ctx, in) + return srv.(CoreServiceServer).UpdateFeatureSetStatus(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/feast.core.CoreService/ListIngestionJobs", + FullMethod: "/feast.core.CoreService/UpdateFeatureSetStatus", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServiceServer).ListIngestionJobs(ctx, req.(*ListIngestionJobsRequest)) + return srv.(CoreServiceServer).UpdateFeatureSetStatus(ctx, req.(*UpdateFeatureSetStatusRequest)) } 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_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).RestartIngestionJob(ctx, in) + return srv.(CoreServiceServer).ApplyFeatureTable(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/feast.core.CoreService/RestartIngestionJob", + FullMethod: "/feast.core.CoreService/ApplyFeatureTable", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServiceServer).RestartIngestionJob(ctx, req.(*RestartIngestionJobRequest)) + return srv.(CoreServiceServer).ApplyFeatureTable(ctx, req.(*ApplyFeatureTableRequest)) } 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_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).StopIngestionJob(ctx, in) + return srv.(CoreServiceServer).GetFeatureTable(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/feast.core.CoreService/StopIngestionJob", + FullMethod: "/feast.core.CoreService/GetFeatureTable", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServiceServer).StopIngestionJob(ctx, req.(*StopIngestionJobRequest)) + return srv.(CoreServiceServer).GetFeatureTable(ctx, req.(*GetFeatureTableRequest)) } return interceptor(ctx, in, info, handler) } @@ -3218,6 +4601,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 +4625,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 +4649,187 @@ var _CoreService_serviceDesc = grpc.ServiceDesc{ MethodName: "ListProjects", Handler: _CoreService_ListProjects_Handler, }, + { + 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", +} + +// 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/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/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/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/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/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/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..1c774ea89f5 100644 --- a/sdk/python/feast/cli.py +++ b/sdk/python/feast/cli.py @@ -25,7 +25,9 @@ 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.feature_table import FeatureTable from feast.loaders.yaml import yaml_loader _logger = logging.getLogger(__name__) @@ -114,6 +116,181 @@ 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="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 b4a36ae8952..713776f1f53 100644 --- a/sdk/python/feast/client.py +++ b/sdk/python/feast/client.py @@ -19,8 +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 @@ -44,27 +42,41 @@ FEAST_DEFAULT_OPTIONS, ) from feast.core.CoreService_pb2 import ( + ApplyEntityRequest, + ApplyEntityResponse, ApplyFeatureSetRequest, ApplyFeatureSetResponse, + ApplyFeatureTableRequest, + ApplyFeatureTableResponse, ArchiveProjectRequest, ArchiveProjectResponse, CreateProjectRequest, CreateProjectResponse, + GetEntityRequest, + GetEntityResponse, GetFeastCoreVersionRequest, GetFeatureSetRequest, GetFeatureSetResponse, GetFeatureStatisticsRequest, + GetFeatureTableRequest, + GetFeatureTableResponse, + ListEntitiesRequest, + ListEntitiesResponse, ListFeatureSetsRequest, ListFeatureSetsResponse, ListFeaturesRequest, ListFeaturesResponse, + ListFeatureTablesRequest, + ListFeatureTablesResponse, ListProjectsRequest, ListProjectsResponse, ) 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.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 @@ -91,8 +103,6 @@ CPU_COUNT: int = multiprocessing.cpu_count() -warnings.simplefilter("once", DeprecationWarning) - class Client: """ @@ -290,6 +300,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): @@ -356,6 +368,243 @@ 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_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 @@ -531,37 +780,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_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 +901,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 +1182,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 +1194,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/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/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/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/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): 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 8d591d26b9e..be8bc786794 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 @@ -984,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/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 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 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 ``` diff --git a/serving/src/main/resources/application.yml b/serving/src/main/resources/application.yml index 734f7b00963..0b4862e18c2 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: @@ -63,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 @@ -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 @@ -130,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} 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 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 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) 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 1c115cba1ef..853da9f5290 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 @@ -897,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], + ], } ) @@ -929,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), ) @@ -981,18 +970,13 @@ 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(): 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 +1118,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 @@ -1198,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 @@ -1432,11 +1412,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( 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), ], )