|
| 1 | +/* |
| 2 | + * SPDX-License-Identifier: Apache-2.0 |
| 3 | + * Copyright 2018-2019 The Feast Authors |
| 4 | + * |
| 5 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | + * you may not use this file except in compliance with the License. |
| 7 | + * You may obtain a copy of the License at |
| 8 | + * |
| 9 | + * https://www.apache.org/licenses/LICENSE-2.0 |
| 10 | + * |
| 11 | + * Unless required by applicable law or agreed to in writing, software |
| 12 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | + * See the License for the specific language governing permissions and |
| 15 | + * limitations under the License. |
| 16 | + */ |
| 17 | +package feast.core.controller; |
| 18 | + |
| 19 | +import com.google.protobuf.InvalidProtocolBufferException; |
| 20 | +import com.google.protobuf.Timestamp; |
| 21 | +import feast.core.config.FeastProperties; |
| 22 | +import feast.core.model.Project; |
| 23 | +import feast.core.service.ProjectService; |
| 24 | +import feast.core.service.SpecService; |
| 25 | +import feast.core.service.StatsService; |
| 26 | +import feast.proto.core.CoreServiceProto.GetFeastCoreVersionResponse; |
| 27 | +import feast.proto.core.CoreServiceProto.GetFeatureStatisticsRequest; |
| 28 | +import feast.proto.core.CoreServiceProto.GetFeatureStatisticsRequest.Builder; |
| 29 | +import feast.proto.core.CoreServiceProto.GetFeatureStatisticsResponse; |
| 30 | +import feast.proto.core.CoreServiceProto.ListFeatureSetsRequest; |
| 31 | +import feast.proto.core.CoreServiceProto.ListFeatureSetsResponse; |
| 32 | +import feast.proto.core.CoreServiceProto.ListFeaturesRequest; |
| 33 | +import feast.proto.core.CoreServiceProto.ListFeaturesResponse; |
| 34 | +import feast.proto.core.CoreServiceProto.ListProjectsResponse; |
| 35 | +import java.io.IOException; |
| 36 | +import java.time.LocalDate; |
| 37 | +import java.time.LocalTime; |
| 38 | +import java.time.ZoneOffset; |
| 39 | +import java.time.format.DateTimeFormatter; |
| 40 | +import java.util.Arrays; |
| 41 | +import java.util.List; |
| 42 | +import java.util.Optional; |
| 43 | +import java.util.stream.Collectors; |
| 44 | +import lombok.extern.slf4j.Slf4j; |
| 45 | +import org.springframework.beans.factory.annotation.Autowired; |
| 46 | +import org.springframework.web.bind.annotation.CrossOrigin; |
| 47 | +import org.springframework.web.bind.annotation.RequestMapping; |
| 48 | +import org.springframework.web.bind.annotation.RequestMethod; |
| 49 | +import org.springframework.web.bind.annotation.RequestParam; |
| 50 | +import org.springframework.web.bind.annotation.RestController; |
| 51 | + |
| 52 | +/** |
| 53 | + * EXPERIMENTAL: Controller for HTTP endpoints to Feast Core. These endpoints are subject to change. |
| 54 | + */ |
| 55 | +@RestController |
| 56 | +@CrossOrigin |
| 57 | +@Slf4j |
| 58 | +@RequestMapping(value = "/api/v1", produces = "application/json") |
| 59 | +public class CoreServiceRestController { |
| 60 | + |
| 61 | + private final FeastProperties feastProperties; |
| 62 | + private SpecService specService; |
| 63 | + private StatsService statsService; |
| 64 | + private ProjectService projectService; |
| 65 | + |
| 66 | + @Autowired |
| 67 | + public CoreServiceRestController( |
| 68 | + FeastProperties feastProperties, |
| 69 | + SpecService specService, |
| 70 | + StatsService statsService, |
| 71 | + ProjectService projectService) { |
| 72 | + this.feastProperties = feastProperties; |
| 73 | + this.specService = specService; |
| 74 | + this.statsService = statsService; |
| 75 | + this.projectService = projectService; |
| 76 | + } |
| 77 | + |
| 78 | + /** |
| 79 | + * GET /version : Fetches the version of Feast Core. |
| 80 | + * |
| 81 | + * @return (200 OK) Returns {@link GetFeastCoreVersionResponse} in JSON. |
| 82 | + */ |
| 83 | + @RequestMapping(value = "/version", method = RequestMethod.GET) |
| 84 | + public GetFeastCoreVersionResponse getVersion() { |
| 85 | + GetFeastCoreVersionResponse response = |
| 86 | + GetFeastCoreVersionResponse.newBuilder().setVersion(feastProperties.getVersion()).build(); |
| 87 | + return response; |
| 88 | + } |
| 89 | + |
| 90 | + /** |
| 91 | + * GET /feature-sets : Retrieve a list of Feature Sets according to filtering parameters of Feast |
| 92 | + * project name and feature set name. If none matches, an empty JSON response is returned. |
| 93 | + * |
| 94 | + * @param project Request Parameter: Name of feast project to search in. If set to <code>"*" |
| 95 | + * </code>, all existing projects will be filtered. However, asterisk can NOT be |
| 96 | + * combined with other strings (for example <code>"merchant_*"</code>) to use as wildcard to |
| 97 | + * filter feature sets. |
| 98 | + * @param name Request Parameter: Feature set name. If set to "*", filter * all feature sets by |
| 99 | + * default. Asterisk can be used as wildcard to filter * feature sets. |
| 100 | + * @return (200 OK) Return {@link ListFeatureSetsResponse} in JSON. |
| 101 | + */ |
| 102 | + @RequestMapping(value = "/feature-sets", method = RequestMethod.GET) |
| 103 | + public ListFeatureSetsResponse listFeatureSets( |
| 104 | + @RequestParam(defaultValue = Project.DEFAULT_NAME) String project, @RequestParam String name) |
| 105 | + throws InvalidProtocolBufferException { |
| 106 | + ListFeatureSetsRequest.Filter.Builder filterBuilder = |
| 107 | + ListFeatureSetsRequest.Filter.newBuilder().setProject(project).setFeatureSetName(name); |
| 108 | + return specService.listFeatureSets(filterBuilder.build()); |
| 109 | + } |
| 110 | + |
| 111 | + /** |
| 112 | + * GET /features : List Features based on project and entities. |
| 113 | + * |
| 114 | + * @param entities Request Parameter: List of all entities every returned feature should belong |
| 115 | + * to. At least one entity is required. For example, if <code>entity1</code> and <code>entity2 |
| 116 | + * </code> are given, then all features returned (if any) will belong to BOTH |
| 117 | + * entities. |
| 118 | + * @param project (Optional) Request Parameter: A single project where the feature set of all |
| 119 | + * features returned is under. If not provided, the default project will be used, usually |
| 120 | + * <code>default</code>. |
| 121 | + * @return (200 OK) Return {@link ListFeaturesResponse} in JSON. |
| 122 | + */ |
| 123 | + @RequestMapping(value = "/features", method = RequestMethod.GET) |
| 124 | + public ListFeaturesResponse listFeatures( |
| 125 | + @RequestParam String[] entities, @RequestParam(required = false) Optional<String> project) { |
| 126 | + ListFeaturesRequest.Filter.Builder filterBuilder = |
| 127 | + ListFeaturesRequest.Filter.newBuilder().addAllEntities(Arrays.asList(entities)); |
| 128 | + project.ifPresent(filterBuilder::setProject); |
| 129 | + return specService.listFeatures(filterBuilder.build()); |
| 130 | + } |
| 131 | + |
| 132 | + /** |
| 133 | + * GET /feature-statistics : Fetches statistics for a dataset speficied by the parameters. Either |
| 134 | + * both (start_date, end_date) need to be given or ingestion_ids are required. If both are given, |
| 135 | + * (start_date, end_date) will be ignored. |
| 136 | + * |
| 137 | + * @param ingestionIds Request Parameter: List of ingestion IDs. If missing, both startDate and |
| 138 | + * endDate should be provided. |
| 139 | + * @param startDate Request Parameter: UTC+0 starting date (inclusive) in the ISO format, from |
| 140 | + * <code>0001-01-01</code> to <code>9999-12-31</code>. Time given will be ignored. This |
| 141 | + * parameter will be ignored if any ingestionIds is provided. |
| 142 | + * @param endDate Request Parameter: UTC+0 ending date (exclusive) in the ISO format, from <code> |
| 143 | + * 0001-01-01</code> to <code>9999-12-31</code>. Time given will be ignored. This parameter |
| 144 | + * will be ignored if any ingestionIds is provided. |
| 145 | + * @param store Request Parameter: The name of the historical store used in Feast Serving. Online |
| 146 | + * store is not allowed. |
| 147 | + * @param featureSetId Request Parameter: Feature set ID, which has the form of <code> |
| 148 | + * project/feature_set_name</code>. |
| 149 | + * @param forceRefresh Request Parameter: whether to override the values in the cache. Accepts |
| 150 | + * <code>true</code>, <code>false</code>. |
| 151 | + * @param features (Optional) Request Parameter: List of features. If none provided, all features |
| 152 | + * in the feature set will be used for statistics. |
| 153 | + * @return (200 OK) Returns {@link GetFeatureStatisticsResponse} in JSON. |
| 154 | + */ |
| 155 | + @RequestMapping(value = "/feature-statistics", method = RequestMethod.GET) |
| 156 | + public GetFeatureStatisticsResponse getFeatureStatistics( |
| 157 | + @RequestParam(name = "feature_set_id") String featureSetId, |
| 158 | + @RequestParam(required = false) Optional<String[]> features, |
| 159 | + @RequestParam String store, |
| 160 | + @RequestParam(name = "start_date", required = false) Optional<String> startDate, |
| 161 | + @RequestParam(name = "end_date", required = false) Optional<String> endDate, |
| 162 | + @RequestParam(name = "ingestion_ids", required = false) Optional<String[]> ingestionIds, |
| 163 | + @RequestParam(name = "force_refresh") boolean forceRefresh) |
| 164 | + throws IOException { |
| 165 | + |
| 166 | + Builder requestBuilder = |
| 167 | + GetFeatureStatisticsRequest.newBuilder() |
| 168 | + .setForceRefresh(forceRefresh) |
| 169 | + .setFeatureSetId(featureSetId) |
| 170 | + .setStore(store); |
| 171 | + |
| 172 | + // set optional request parameters if they are provided |
| 173 | + features.ifPresent(theFeatures -> requestBuilder.addAllFeatures(Arrays.asList(theFeatures))); |
| 174 | + startDate.ifPresent( |
| 175 | + startDateStr -> requestBuilder.setStartDate(utcTimeStringToTimestamp(startDateStr))); |
| 176 | + endDate.ifPresent( |
| 177 | + endDateStr -> requestBuilder.setEndDate(utcTimeStringToTimestamp(endDateStr))); |
| 178 | + ingestionIds.ifPresent( |
| 179 | + theIngestionIds -> requestBuilder.addAllIngestionIds(Arrays.asList(theIngestionIds))); |
| 180 | + |
| 181 | + return statsService.getFeatureStatistics(requestBuilder.build()); |
| 182 | + } |
| 183 | + |
| 184 | + /** |
| 185 | + * GET /projects : Get the list of existing feast projects. |
| 186 | + * |
| 187 | + * @return (200 OK) Returns {@link ListProjectsResponse} in JSON. |
| 188 | + */ |
| 189 | + @RequestMapping(value = "/projects", method = RequestMethod.GET) |
| 190 | + public ListProjectsResponse listProjects() { |
| 191 | + List<Project> projects = projectService.listProjects(); |
| 192 | + return ListProjectsResponse.newBuilder() |
| 193 | + .addAllProjects(projects.stream().map(Project::getName).collect(Collectors.toList())) |
| 194 | + .build(); |
| 195 | + } |
| 196 | + |
| 197 | + private Timestamp utcTimeStringToTimestamp(String utcTimeString) { |
| 198 | + long epochSecond = |
| 199 | + LocalDate.parse(utcTimeString, DateTimeFormatter.ISO_DATE) |
| 200 | + .toEpochSecond(LocalTime.MIN, ZoneOffset.UTC); |
| 201 | + return Timestamp.newBuilder().setSeconds(epochSecond).setNanos(0).build(); |
| 202 | + } |
| 203 | +} |
0 commit comments