Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ protected void configure(HttpSecurity http) throws Exception {

if (feastProperties.securityProperties().isDisableRestControllerAuth()) {
matchersToBypass.add("/api/v1/**");
matchersToBypass.add("/api/v2/**");
}

// Bypasses security/authentication for the following paths
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,12 @@
import feast.proto.core.CoreServiceProto.GetFeatureStatisticsRequest;
import feast.proto.core.CoreServiceProto.GetFeatureStatisticsRequest.Builder;
import feast.proto.core.CoreServiceProto.GetFeatureStatisticsResponse;
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.ListProjectsResponse;
Expand All @@ -55,7 +59,7 @@
@RestController
@CrossOrigin
@Slf4j
@RequestMapping(value = "/api/v1", produces = "application/json")
@RequestMapping(value = "/api", produces = "application/json")
public class CoreServiceRestController {

private final FeastProperties feastProperties;
Expand All @@ -80,7 +84,7 @@ public CoreServiceRestController(
*
* @return (200 OK) Returns {@link GetFeastCoreVersionResponse} in JSON.
*/
@RequestMapping(value = "/version", method = RequestMethod.GET)
@RequestMapping(value = "/v1/version", method = RequestMethod.GET)
public GetFeastCoreVersionResponse getVersion() {
GetFeastCoreVersionResponse response =
GetFeastCoreVersionResponse.newBuilder().setVersion(feastProperties.getVersion()).build();
Expand All @@ -99,7 +103,7 @@ public GetFeastCoreVersionResponse getVersion() {
* default. Asterisk can be used as wildcard to filter * feature sets.
* @return (200 OK) Return {@link ListFeatureSetsResponse} in JSON.
*/
@RequestMapping(value = "/feature-sets", method = RequestMethod.GET)
@RequestMapping(value = "/v1/feature-sets", method = RequestMethod.GET)
public ListFeatureSetsResponse listFeatureSets(
@RequestParam(defaultValue = Project.DEFAULT_NAME) String project, @RequestParam String name)
throws InvalidProtocolBufferException {
Expand All @@ -120,7 +124,7 @@ public ListFeatureSetsResponse listFeatureSets(
* <code>default</code>.
* @return (200 OK) Return {@link ListFeaturesResponse} in JSON.
*/
@RequestMapping(value = "/features", method = RequestMethod.GET)
@RequestMapping(value = "/v1/features", method = RequestMethod.GET)
public ListFeaturesResponse listFeatures(
@RequestParam String[] entities, @RequestParam(required = false) Optional<String> project) {
ListFeaturesRequest.Filter.Builder filterBuilder =
Expand Down Expand Up @@ -152,7 +156,7 @@ public ListFeaturesResponse listFeatures(
* in the feature set will be used for statistics.
* @return (200 OK) Returns {@link GetFeatureStatisticsResponse} in JSON.
*/
@RequestMapping(value = "/feature-statistics", method = RequestMethod.GET)
@RequestMapping(value = "/v1/feature-statistics", method = RequestMethod.GET)
public GetFeatureStatisticsResponse getFeatureStatistics(
@RequestParam(name = "feature_set_id") String featureSetId,
@RequestParam(required = false) Optional<String[]> features,
Expand Down Expand Up @@ -186,14 +190,44 @@ public GetFeatureStatisticsResponse getFeatureStatistics(
*
* @return (200 OK) Returns {@link ListProjectsResponse} in JSON.
*/
@RequestMapping(value = "/projects", method = RequestMethod.GET)
@RequestMapping(value = "/v1/projects", method = RequestMethod.GET)
public ListProjectsResponse listProjects() {
List<Project> projects = projectService.listProjects();
return ListProjectsResponse.newBuilder()
.addAllProjects(projects.stream().map(Project::getName).collect(Collectors.toList()))
.build();
}

/**
* GET /entities : Retrieve a list of Entities according to filtering parameters of Feast project
* name. If none matches, an empty JSON response is returned.
*
* @param project Request Parameter: Name of feast project to search in.
* @return (200 OK) Return {@link ListEntitiesResponse} in JSON.
*/
@RequestMapping(value = "/v2/entities", method = RequestMethod.GET)
public ListEntitiesResponse listEntities(
@RequestParam(defaultValue = Project.DEFAULT_NAME) String project) {
ListEntitiesRequest.Filter.Builder filterBuilder =
ListEntitiesRequest.Filter.newBuilder().setProject(project);
return specService.listEntities(filterBuilder.build());
}

/**
* GET /feature-tables : Retrieve a list of Feature Tables according to filtering parameters of
* Feast project name. If none matches, an empty JSON response is returned.
*
* @param project Request Parameter: Name of feast project to search in.
* @return (200 OK) Return {@link ListFeatureTablesResponse} in JSON.
*/
@RequestMapping(value = "/v2/feature-tables", method = RequestMethod.GET)
public ListFeatureTablesResponse listFeatureTables(
@RequestParam(defaultValue = Project.DEFAULT_NAME) String project) {
ListFeatureTablesRequest.Filter.Builder filterBuilder =
ListFeatureTablesRequest.Filter.newBuilder().setProject(project);
return specService.listFeatureTables(filterBuilder.build());
}

private Timestamp utcTimeStringToTimestamp(String utcTimeString) {
long epochSecond =
LocalDate.parse(utcTimeString, DateTimeFormatter.ISO_DATE)
Expand Down
121 changes: 87 additions & 34 deletions core/src/test/java/feast/core/controller/CoreServiceRestIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,18 @@
import feast.common.it.SimpleCoreClient;
import feast.core.model.Project;
import feast.proto.core.CoreServiceGrpc;
import feast.proto.core.EntityProto;
import feast.proto.core.FeatureSetProto.FeatureSet;
import feast.proto.core.FeatureTableProto;
import feast.proto.types.ValueProto;
import feast.proto.types.ValueProto.ValueType.Enum;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import io.restassured.path.json.JsonPath;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
Expand Down Expand Up @@ -205,19 +210,53 @@ public void listFeatures() {
.body("features", aMapWithSize(2));
}

@Test
public void listEntities() {
String uri1 =
UriComponentsBuilder.fromPath("/api/v2/entities")
.queryParam("project", "default")
.buildAndExpand()
.toString();
String responseBody =
get(uri1)
.then()
.log()
.everything()
.assertThat()
.contentType(ContentType.JSON)
.extract()
.response()
.getBody()
.asString();
List<String> entityList = JsonPath.from(responseBody).getList("entities");
assertEquals(entityList.size(), 2);
}

@Test
public void listFeatureTables() {
String uri1 =
UriComponentsBuilder.fromPath("/api/v2/feature-tables")
.queryParam("project", "default")
.buildAndExpand()
.toString();
String responseBody =
get(uri1)
.then()
.log()
.everything()
.assertThat()
.contentType(ContentType.JSON)
.extract()
.response()
.getBody()
.asString();
List<String> featureTableList = JsonPath.from(responseBody).getList("tables");
assertEquals(featureTableList.size(), 1);
}

@BeforeEach
private void createFakeFeatureSets() {
// spec:
// name: merchant_ratings
// entities:
// - name: merchant_id
// valueType: STRING
// features:
// - name: average_rating
// valueType: DOUBLE
// - name: total_ratings
// valueType: INT64
// project: default
private void createSpecs() {
// Apply feature sets
FeatureSet merchantFeatureSet =
DataGenerator.createFeatureSet(
DataGenerator.getDefaultSource(),
Expand All @@ -227,17 +266,6 @@ private void createFakeFeatureSets() {
ImmutableMap.of("average_rating", Enum.DOUBLE, "total_ratings", Enum.INT64));
apiClient.simpleApplyFeatureSet(merchantFeatureSet);

// spec:
// name: another_merchant_ratings
// entities:
// - name: merchant_id
// valueType: STRING
// features:
// - name: another_average_rating
// valueType: DOUBLE
// - name: another_total_ratings
// valueType: INT64
// project: default
FeatureSet anotherMerchantFeatureSet =
DataGenerator.createFeatureSet(
DataGenerator.getDefaultSource(),
Expand All @@ -249,17 +277,6 @@ private void createFakeFeatureSets() {
"another_total_ratings", Enum.INT64));
apiClient.simpleApplyFeatureSet(anotherMerchantFeatureSet);

// spec:
// name: yet_another_merchant_feature_set
// entities:
// - name: merchant_id
// valueType: STRING
// features:
// - name: merchant_prop1
// valueType: BOOL
// - name: merchant_prop2
// valueType: FLOAT
// project: merchant
FeatureSet yetAnotherMerchantFeatureSet =
DataGenerator.createFeatureSet(
DataGenerator.getDefaultSource(),
Expand All @@ -268,6 +285,42 @@ private void createFakeFeatureSets() {
ImmutableMap.of("merchant_id", Enum.STRING),
ImmutableMap.of("merchant_prop1", Enum.BOOL, "merchant_prop2", Enum.FLOAT));
apiClient.simpleApplyFeatureSet(yetAnotherMerchantFeatureSet);

// Apply entities
EntityProto.EntitySpecV2 entitySpec1 =
DataGenerator.createEntitySpecV2(
"entity1",
"Entity 1 description",
ValueProto.ValueType.Enum.STRING,
avro.shaded.com.google.common.collect.ImmutableMap.of("label_key", "label_value"));
EntityProto.EntitySpecV2 entitySpec2 =
DataGenerator.createEntitySpecV2(
"entity2",
"Entity 2 description",
ValueProto.ValueType.Enum.STRING,
avro.shaded.com.google.common.collect.ImmutableMap.of("label_key2", "label_value2"));
apiClient.simpleApplyEntity("default", entitySpec1);
apiClient.simpleApplyEntity("default", entitySpec2);

// Apply feature table
FeatureTableProto.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", "ts_col", ""))
.build();
apiClient.applyFeatureTable("default", featureTableSpec);

RestAssured.port = port;
}
}