Skip to content

Commit 0503404

Browse files
committed
Update rest endpoints
Signed-off-by: Terence <terencelimxp@gmail.com>
1 parent 8d1326f commit 0503404

3 files changed

Lines changed: 128 additions & 40 deletions

File tree

core/src/main/java/feast/core/config/WebSecurityConfig.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ protected void configure(HttpSecurity http) throws Exception {
5151

5252
if (feastProperties.securityProperties().isDisableRestControllerAuth()) {
5353
matchersToBypass.add("/api/v1/**");
54+
matchersToBypass.add("/api/v2/**");
5455
}
5556

5657
// Bypasses security/authentication for the following paths

core/src/main/java/feast/core/controller/CoreServiceRestController.java

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,12 @@
2727
import feast.proto.core.CoreServiceProto.GetFeatureStatisticsRequest;
2828
import feast.proto.core.CoreServiceProto.GetFeatureStatisticsRequest.Builder;
2929
import feast.proto.core.CoreServiceProto.GetFeatureStatisticsResponse;
30+
import feast.proto.core.CoreServiceProto.ListEntitiesRequest;
31+
import feast.proto.core.CoreServiceProto.ListEntitiesResponse;
3032
import feast.proto.core.CoreServiceProto.ListFeatureSetsRequest;
3133
import feast.proto.core.CoreServiceProto.ListFeatureSetsResponse;
34+
import feast.proto.core.CoreServiceProto.ListFeatureTablesRequest;
35+
import feast.proto.core.CoreServiceProto.ListFeatureTablesResponse;
3236
import feast.proto.core.CoreServiceProto.ListFeaturesRequest;
3337
import feast.proto.core.CoreServiceProto.ListFeaturesResponse;
3438
import feast.proto.core.CoreServiceProto.ListProjectsResponse;
@@ -55,7 +59,7 @@
5559
@RestController
5660
@CrossOrigin
5761
@Slf4j
58-
@RequestMapping(value = "/api/v1", produces = "application/json")
62+
@RequestMapping(value = "/api", produces = "application/json")
5963
public class CoreServiceRestController {
6064

6165
private final FeastProperties feastProperties;
@@ -80,7 +84,7 @@ public CoreServiceRestController(
8084
*
8185
* @return (200 OK) Returns {@link GetFeastCoreVersionResponse} in JSON.
8286
*/
83-
@RequestMapping(value = "/version", method = RequestMethod.GET)
87+
@RequestMapping(value = "/v1/version", method = RequestMethod.GET)
8488
public GetFeastCoreVersionResponse getVersion() {
8589
GetFeastCoreVersionResponse response =
8690
GetFeastCoreVersionResponse.newBuilder().setVersion(feastProperties.getVersion()).build();
@@ -99,7 +103,7 @@ public GetFeastCoreVersionResponse getVersion() {
99103
* default. Asterisk can be used as wildcard to filter * feature sets.
100104
* @return (200 OK) Return {@link ListFeatureSetsResponse} in JSON.
101105
*/
102-
@RequestMapping(value = "/feature-sets", method = RequestMethod.GET)
106+
@RequestMapping(value = "/v1/feature-sets", method = RequestMethod.GET)
103107
public ListFeatureSetsResponse listFeatureSets(
104108
@RequestParam(defaultValue = Project.DEFAULT_NAME) String project, @RequestParam String name)
105109
throws InvalidProtocolBufferException {
@@ -120,7 +124,7 @@ public ListFeatureSetsResponse listFeatureSets(
120124
* <code>default</code>.
121125
* @return (200 OK) Return {@link ListFeaturesResponse} in JSON.
122126
*/
123-
@RequestMapping(value = "/features", method = RequestMethod.GET)
127+
@RequestMapping(value = "/v1/features", method = RequestMethod.GET)
124128
public ListFeaturesResponse listFeatures(
125129
@RequestParam String[] entities, @RequestParam(required = false) Optional<String> project) {
126130
ListFeaturesRequest.Filter.Builder filterBuilder =
@@ -152,7 +156,7 @@ public ListFeaturesResponse listFeatures(
152156
* in the feature set will be used for statistics.
153157
* @return (200 OK) Returns {@link GetFeatureStatisticsResponse} in JSON.
154158
*/
155-
@RequestMapping(value = "/feature-statistics", method = RequestMethod.GET)
159+
@RequestMapping(value = "/v1/feature-statistics", method = RequestMethod.GET)
156160
public GetFeatureStatisticsResponse getFeatureStatistics(
157161
@RequestParam(name = "feature_set_id") String featureSetId,
158162
@RequestParam(required = false) Optional<String[]> features,
@@ -186,14 +190,44 @@ public GetFeatureStatisticsResponse getFeatureStatistics(
186190
*
187191
* @return (200 OK) Returns {@link ListProjectsResponse} in JSON.
188192
*/
189-
@RequestMapping(value = "/projects", method = RequestMethod.GET)
193+
@RequestMapping(value = "/v1/projects", method = RequestMethod.GET)
190194
public ListProjectsResponse listProjects() {
191195
List<Project> projects = projectService.listProjects();
192196
return ListProjectsResponse.newBuilder()
193197
.addAllProjects(projects.stream().map(Project::getName).collect(Collectors.toList()))
194198
.build();
195199
}
196200

201+
/**
202+
* GET /entities : Retrieve a list of Entities according to filtering parameters of Feast project
203+
* name. If none matches, an empty JSON response is returned.
204+
*
205+
* @param project Request Parameter: Name of feast project to search in.
206+
* @return (200 OK) Return {@link ListEntitiesResponse} in JSON.
207+
*/
208+
@RequestMapping(value = "/v2/entities", method = RequestMethod.GET)
209+
public ListEntitiesResponse listEntities(
210+
@RequestParam(defaultValue = Project.DEFAULT_NAME) String project) {
211+
ListEntitiesRequest.Filter.Builder filterBuilder =
212+
ListEntitiesRequest.Filter.newBuilder().setProject(project);
213+
return specService.listEntities(filterBuilder.build());
214+
}
215+
216+
/**
217+
* GET /feature-tables : Retrieve a list of Feature Tables according to filtering parameters of
218+
* Feast project name. If none matches, an empty JSON response is returned.
219+
*
220+
* @param project Request Parameter: Name of feast project to search in.
221+
* @return (200 OK) Return {@link ListFeatureTablesResponse} in JSON.
222+
*/
223+
@RequestMapping(value = "/v2/feature-tables", method = RequestMethod.GET)
224+
public ListFeatureTablesResponse listFeatureTables(
225+
@RequestParam(defaultValue = Project.DEFAULT_NAME) String project) {
226+
ListFeatureTablesRequest.Filter.Builder filterBuilder =
227+
ListFeatureTablesRequest.Filter.newBuilder().setProject(project);
228+
return specService.listFeatureTables(filterBuilder.build());
229+
}
230+
197231
private Timestamp utcTimeStringToTimestamp(String utcTimeString) {
198232
long epochSecond =
199233
LocalDate.parse(utcTimeString, DateTimeFormatter.ISO_DATE)

core/src/test/java/feast/core/controller/CoreServiceRestIT.java

Lines changed: 87 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,18 @@
2727
import feast.common.it.SimpleCoreClient;
2828
import feast.core.model.Project;
2929
import feast.proto.core.CoreServiceGrpc;
30+
import feast.proto.core.EntityProto;
3031
import feast.proto.core.FeatureSetProto.FeatureSet;
32+
import feast.proto.core.FeatureTableProto;
33+
import feast.proto.types.ValueProto;
3134
import feast.proto.types.ValueProto.ValueType.Enum;
3235
import io.grpc.ManagedChannel;
3336
import io.grpc.ManagedChannelBuilder;
3437
import io.restassured.RestAssured;
3538
import io.restassured.http.ContentType;
3639
import io.restassured.path.json.JsonPath;
40+
import java.util.Arrays;
41+
import java.util.HashMap;
3742
import java.util.List;
3843
import org.junit.jupiter.api.BeforeAll;
3944
import org.junit.jupiter.api.BeforeEach;
@@ -205,19 +210,53 @@ public void listFeatures() {
205210
.body("features", aMapWithSize(2));
206211
}
207212

213+
@Test
214+
public void listEntities() {
215+
String uri1 =
216+
UriComponentsBuilder.fromPath("/api/v2/entities")
217+
.queryParam("project", "default")
218+
.buildAndExpand()
219+
.toString();
220+
String responseBody =
221+
get(uri1)
222+
.then()
223+
.log()
224+
.everything()
225+
.assertThat()
226+
.contentType(ContentType.JSON)
227+
.extract()
228+
.response()
229+
.getBody()
230+
.asString();
231+
List<String> entityList = JsonPath.from(responseBody).getList("entities");
232+
assertEquals(entityList.size(), 2);
233+
}
234+
235+
@Test
236+
public void listFeatureTables() {
237+
String uri1 =
238+
UriComponentsBuilder.fromPath("/api/v2/feature-tables")
239+
.queryParam("project", "default")
240+
.buildAndExpand()
241+
.toString();
242+
String responseBody =
243+
get(uri1)
244+
.then()
245+
.log()
246+
.everything()
247+
.assertThat()
248+
.contentType(ContentType.JSON)
249+
.extract()
250+
.response()
251+
.getBody()
252+
.asString();
253+
List<String> featureTableList = JsonPath.from(responseBody).getList("tables");
254+
assertEquals(featureTableList.size(), 1);
255+
}
256+
208257
@BeforeEach
209-
private void createFakeFeatureSets() {
210-
// spec:
211-
// name: merchant_ratings
212-
// entities:
213-
// - name: merchant_id
214-
// valueType: STRING
215-
// features:
216-
// - name: average_rating
217-
// valueType: DOUBLE
218-
// - name: total_ratings
219-
// valueType: INT64
220-
// project: default
258+
private void createSpecs() {
259+
// Apply feature sets
221260
FeatureSet merchantFeatureSet =
222261
DataGenerator.createFeatureSet(
223262
DataGenerator.getDefaultSource(),
@@ -227,17 +266,6 @@ private void createFakeFeatureSets() {
227266
ImmutableMap.of("average_rating", Enum.DOUBLE, "total_ratings", Enum.INT64));
228267
apiClient.simpleApplyFeatureSet(merchantFeatureSet);
229268

230-
// spec:
231-
// name: another_merchant_ratings
232-
// entities:
233-
// - name: merchant_id
234-
// valueType: STRING
235-
// features:
236-
// - name: another_average_rating
237-
// valueType: DOUBLE
238-
// - name: another_total_ratings
239-
// valueType: INT64
240-
// project: default
241269
FeatureSet anotherMerchantFeatureSet =
242270
DataGenerator.createFeatureSet(
243271
DataGenerator.getDefaultSource(),
@@ -249,17 +277,6 @@ private void createFakeFeatureSets() {
249277
"another_total_ratings", Enum.INT64));
250278
apiClient.simpleApplyFeatureSet(anotherMerchantFeatureSet);
251279

252-
// spec:
253-
// name: yet_another_merchant_feature_set
254-
// entities:
255-
// - name: merchant_id
256-
// valueType: STRING
257-
// features:
258-
// - name: merchant_prop1
259-
// valueType: BOOL
260-
// - name: merchant_prop2
261-
// valueType: FLOAT
262-
// project: merchant
263280
FeatureSet yetAnotherMerchantFeatureSet =
264281
DataGenerator.createFeatureSet(
265282
DataGenerator.getDefaultSource(),
@@ -268,6 +285,42 @@ private void createFakeFeatureSets() {
268285
ImmutableMap.of("merchant_id", Enum.STRING),
269286
ImmutableMap.of("merchant_prop1", Enum.BOOL, "merchant_prop2", Enum.FLOAT));
270287
apiClient.simpleApplyFeatureSet(yetAnotherMerchantFeatureSet);
288+
289+
// Apply entities
290+
EntityProto.EntitySpecV2 entitySpec1 =
291+
DataGenerator.createEntitySpecV2(
292+
"entity1",
293+
"Entity 1 description",
294+
ValueProto.ValueType.Enum.STRING,
295+
avro.shaded.com.google.common.collect.ImmutableMap.of("label_key", "label_value"));
296+
EntityProto.EntitySpecV2 entitySpec2 =
297+
DataGenerator.createEntitySpecV2(
298+
"entity2",
299+
"Entity 2 description",
300+
ValueProto.ValueType.Enum.STRING,
301+
avro.shaded.com.google.common.collect.ImmutableMap.of("label_key2", "label_value2"));
302+
apiClient.simpleApplyEntity("default", entitySpec1);
303+
apiClient.simpleApplyEntity("default", entitySpec2);
304+
305+
// Apply feature table
306+
FeatureTableProto.FeatureTableSpec featureTableSpec =
307+
DataGenerator.createFeatureTableSpec(
308+
"featuretable1",
309+
Arrays.asList("entity1", "entity2"),
310+
new HashMap<>() {
311+
{
312+
put("feature1", ValueProto.ValueType.Enum.STRING);
313+
put("feature2", ValueProto.ValueType.Enum.FLOAT);
314+
}
315+
},
316+
7200,
317+
ImmutableMap.of("feat_key2", "feat_value2"))
318+
.toBuilder()
319+
.setBatchSource(
320+
DataGenerator.createFileDataSourceSpec("file:///path/to/file", "ts_col", ""))
321+
.build();
322+
apiClient.applyFeatureTable("default", featureTableSpec);
323+
271324
RestAssured.port = port;
272325
}
273326
}

0 commit comments

Comments
 (0)