From 068389d829866063be741a5fc82ef18bc832698b Mon Sep 17 00:00:00 2001 From: Achal Shah Date: Thu, 30 Dec 2021 01:14:01 +0530 Subject: [PATCH] Remove spring-boot from the feast serving application (#2127) * Remove spring-boot from the feast serving application Signed-off-by: Achal Shah * Remove unused spring-based classes Signed-off-by: Achal Shah * Fix grpc implementation Signed-off-by: Achal Shah * Remove verbose logging Signed-off-by: Achal Shah * Add back integration test and test gcs and s3 for real Signed-off-by: Achal Shah * Read from port Signed-off-by: Achal Shah --- java/datatypes/java/pom.xml | 4 + java/pom.xml | 12 + java/serving/pom.xml | 93 ++---- .../feast/serving/ServingApplication.java | 38 --- .../serving/ServingGuiceApplication.java | 46 +++ ...erties.java => ApplicationProperties.java} | 305 ++++++++---------- .../config/ApplicationPropertiesModule.java | 44 +++ .../serving/config/InstrumentationConfig.java | 37 +-- .../feast/serving/config/RegistryConfig.java | 48 +-- .../feast/serving/config/ServerModule.java | 46 +++ .../config/ServingServiceConfigV2.java | 18 +- .../ServingServiceGRpcController.java | 23 +- .../ServingServiceRestController.java | 10 +- .../grpc/OnlineServingGrpcServiceV2.java | 48 +++ .../feast/serving/modules/ServerModule.java | 19 ++ .../serving/registry/RegistryRepository.java | 7 +- .../src/main/resources/application.yml | 27 +- .../ServingServiceGRpcControllerTest.java | 97 ------ .../java/feast/serving/it/ServingBase.java | 147 ++++++--- .../serving/it/ServingRedisGSRegistryIT.java | 48 +-- .../it/ServingRedisLocalRegistryIT.java | 49 +-- .../serving/it/ServingRedisS3RegistryIT.java | 58 ++-- 22 files changed, 649 insertions(+), 575 deletions(-) delete mode 100644 java/serving/src/main/java/feast/serving/ServingApplication.java create mode 100644 java/serving/src/main/java/feast/serving/ServingGuiceApplication.java rename java/serving/src/main/java/feast/serving/config/{FeastProperties.java => ApplicationProperties.java} (56%) create mode 100644 java/serving/src/main/java/feast/serving/config/ApplicationPropertiesModule.java create mode 100644 java/serving/src/main/java/feast/serving/config/ServerModule.java create mode 100644 java/serving/src/main/java/feast/serving/grpc/OnlineServingGrpcServiceV2.java create mode 100644 java/serving/src/main/java/feast/serving/modules/ServerModule.java delete mode 100644 java/serving/src/test/java/feast/serving/controller/ServingServiceGRpcControllerTest.java diff --git a/java/datatypes/java/pom.xml b/java/datatypes/java/pom.xml index 0d165f0cd5..fe6c380a10 100644 --- a/java/datatypes/java/pom.xml +++ b/java/datatypes/java/pom.xml @@ -26,6 +26,10 @@ their interchanges. These are generated from Protocol Buffers and gRPC definitions included in the package. + + 11 + 11 + datatypes-java diff --git a/java/pom.xml b/java/pom.xml index 37dd958f4e..ead8af1309 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -156,6 +156,12 @@ ${commons.lang3.version} + + com.google.inject + guice + 5.0.1 + + com.google.cloud @@ -450,6 +456,12 @@ log4j-slf4j-impl ${log4jVersion} + + org.slf4j + slf4j-api + 1.7.30 + + org.slf4j @@ -91,46 +117,15 @@ - org.springframework.boot - spring-boot-configuration-processor - true + org.slf4j + slf4j-simple - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-log4j2 - org.apache.logging.log4j log4j-web - - org.springframework.boot - spring-boot-devtools - true - - - - - org.springframework.boot - spring-boot-starter-actuator - - - - - org.springframework.boot - spring-boot-test - 2.3.1.RELEASE - test - - io.grpc @@ -199,11 +194,6 @@ simpleclient_servlet 0.8.0 - - io.prometheus - simpleclient_spring_boot - 0.8.0 - com.google.auto.value @@ -242,18 +232,6 @@ io.grpc grpc-testing - - - org.springframework.boot - spring-boot-starter-test - test - - - org.junit.vintage - junit-vintage-engine - - - org.mockito @@ -315,11 +293,6 @@ jakarta.validation-api ${jakarta.validation.api.version} - - net.devh - grpc-server-spring-boot-starter - ${grpc.spring.boot.starter.version} - org.testcontainers testcontainers diff --git a/java/serving/src/main/java/feast/serving/ServingApplication.java b/java/serving/src/main/java/feast/serving/ServingApplication.java deleted file mode 100644 index ab036d04d1..0000000000 --- a/java/serving/src/main/java/feast/serving/ServingApplication.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright 2018-2019 The Feast Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package feast.serving; - -import feast.serving.config.FeastProperties; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; -import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; -import org.springframework.boot.context.properties.EnableConfigurationProperties; - -@SpringBootApplication( - exclude = { - DataSourceAutoConfiguration.class, - DataSourceTransactionManagerAutoConfiguration.class, - HibernateJpaAutoConfiguration.class - }) -@EnableConfigurationProperties(FeastProperties.class) -public class ServingApplication { - public static void main(String[] args) { - SpringApplication.run(ServingApplication.class, args); - } -} diff --git a/java/serving/src/main/java/feast/serving/ServingGuiceApplication.java b/java/serving/src/main/java/feast/serving/ServingGuiceApplication.java new file mode 100644 index 0000000000..224c3e8e55 --- /dev/null +++ b/java/serving/src/main/java/feast/serving/ServingGuiceApplication.java @@ -0,0 +1,46 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2021 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.serving; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import feast.serving.config.*; +import io.grpc.Server; +import java.io.IOException; + +public class ServingGuiceApplication { + + public static void main(String[] args) throws InterruptedException, IOException { + if (args.length == 0) { + throw new RuntimeException( + "Path to application configuration file needs to be specifed via CLI"); + } + + final Injector i = + Guice.createInjector( + new ServingServiceConfigV2(), + new RegistryConfig(), + new InstrumentationConfig(), + new ServerModule(), + new ApplicationPropertiesModule(args)); + + Server server = i.getInstance(Server.class); + + server.start(); + server.awaitTermination(); + } +} diff --git a/java/serving/src/main/java/feast/serving/config/FeastProperties.java b/java/serving/src/main/java/feast/serving/config/ApplicationProperties.java similarity index 56% rename from java/serving/src/main/java/feast/serving/config/FeastProperties.java rename to java/serving/src/main/java/feast/serving/config/ApplicationProperties.java index a9bcb73c3b..4d822d8dbc 100644 --- a/java/serving/src/main/java/feast/serving/config/FeastProperties.java +++ b/java/serving/src/main/java/feast/serving/config/ApplicationProperties.java @@ -31,160 +31,141 @@ import javax.validation.*; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.info.BuildProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; /** Feast Serving properties. */ -@ComponentScan("feast.common.logging") -@ConfigurationProperties(prefix = "feast", ignoreInvalidFields = true) -public class FeastProperties { +public class ApplicationProperties { + public static class FeastProperties { + /* Feast Serving build version */ + @NotBlank private String version = "unknown"; - /** - * Instantiates a new Feast Serving properties. - * - * @param buildProperties the build properties - */ - @Autowired - public FeastProperties(BuildProperties buildProperties) { - setVersion(buildProperties.getVersion()); - } - - /** Instantiates a new Feast class. */ - public FeastProperties() {} - - /* Feast Serving build version */ - @NotBlank private String version = "unknown"; + public void setRegistry(String registry) { + this.registry = registry; + } - @NotBlank private String registry; + public void setRegistryRefreshInterval(int registryRefreshInterval) { + this.registryRefreshInterval = registryRefreshInterval; + } - public String getRegistry() { - return registry; - } + @NotBlank private String registry; - public void setRegistry(final String registry) { - this.registry = registry; - } + public String getRegistry() { + return registry; + } - private int registryRefreshInterval; + private int registryRefreshInterval; - public int getRegistryRefreshInterval() { - return registryRefreshInterval; - } + public int getRegistryRefreshInterval() { + return registryRefreshInterval; + } - public void setRegistryRefreshInterval(final int registryRefreshInterval) { - this.registryRefreshInterval = registryRefreshInterval; - } + /** + * Finds and returns the active store + * + * @return Returns the {@link Store} model object + */ + public Store getActiveStore() { + for (Store store : getStores()) { + if (activeStore.equals(store.getName())) { + return store; + } + } + throw new RuntimeException( + String.format("Active store is misconfigured. Could not find store: %s.", activeStore)); + } - private String gcpProject; + public void setActiveStore(String activeStore) { + this.activeStore = activeStore; + } - public String getGcpProject() { - return gcpProject; - } + /** Name of the active store configuration (only one store can be active at a time). */ + @NotBlank private String activeStore; - public void setGcpProject(final String gcpProject) { - this.gcpProject = gcpProject; - } + /** + * Collection of store configurations. The active store is selected by the "activeStore" field. + */ + private List stores = new ArrayList<>(); - private String awsRegion; + /* Metric tracing properties. */ + private TracingProperties tracing; - public String getAwsRegion() { - return awsRegion; - } + /* Feast Audit Logging properties */ + @NotNull private LoggingProperties logging; - public void setAwsRegion(final String awsRegion) { - this.awsRegion = awsRegion; - } + public void setStores(List stores) { + this.stores = stores; + } - private String transformationServiceEndpoint; + /** + * Gets Serving store configuration as a list of {@link Store}. + * + * @return List of stores objects + */ + public List getStores() { + return stores; + } - public String getTransformationServiceEndpoint() { - return transformationServiceEndpoint; - } + /** + * Gets Feast Serving build version. + * + * @return the build version + */ + public String getVersion() { + return version; + } - public void setTransformationServiceEndpoint(final String transformationServiceEndpoint) { - this.transformationServiceEndpoint = transformationServiceEndpoint; - } + public void setTracing(TracingProperties tracing) { + this.tracing = tracing; + } - /** - * Finds and returns the active store - * - * @return Returns the {@link Store} model object - */ - public Store getActiveStore() { - for (Store store : getStores()) { - if (activeStore.equals(store.getName())) { - return store; - } + /** + * Gets tracing properties + * + * @return tracing properties + */ + public TracingProperties getTracing() { + return tracing; } - throw new RuntimeException( - String.format("Active store is misconfigured. Could not find store: %s.", activeStore)); - } - /** - * Set the name of the active store found in the "stores" configuration list - * - * @param activeStore String name to active store - */ - public void setActiveStore(String activeStore) { - this.activeStore = activeStore; + /** + * Gets logging properties + * + * @return logging properties + */ + public LoggingProperties getLogging() { + return logging; + } } - /** Name of the active store configuration (only one store can be active at a time). */ - @NotBlank private String activeStore; + private FeastProperties feast; - /** - * Collection of store configurations. The active store is selected by the "activeStore" field. - */ - private List stores = new ArrayList<>(); + public void setFeast(FeastProperties feast) { + this.feast = feast; + } - /* Metric tracing properties. */ - private TracingProperties tracing; + public FeastProperties getFeast() { + return feast; + } - /* Feast Audit Logging properties */ - @NotNull private LoggingProperties logging; + private String gcpProject; - @Bean - LoggingProperties loggingProperties() { - return getLogging(); + public String getGcpProject() { + return gcpProject; } - /** - * Gets Serving store configuration as a list of {@link Store}. - * - * @return List of stores objects - */ - public List getStores() { - return stores; + public void setAwsRegion(String awsRegion) { + this.awsRegion = awsRegion; } - /** - * Gets Feast Serving build version. - * - * @return the build version - */ - public String getVersion() { - return version; - } + private String awsRegion; - /** - * Sets build version - * - * @param version the build version - */ - public void setVersion(String version) { - this.version = version; + public String getAwsRegion() { + return awsRegion; } - /** - * Sets the collection of configured stores. - * - * @param stores List of {@link Store} - */ - public void setStores(List stores) { - this.stores = stores; + private String transformationServiceEndpoint; + + public String getTransformationServiceEndpoint() { + return transformationServiceEndpoint; } /** Store configuration class for database that this Feast Serving uses. */ @@ -196,6 +177,12 @@ public static class Store { private Map config = new HashMap<>(); + public Store(String name, String type, Map config) { + this.name = name; + this.type = type; + this.config = config; + } + /** * Gets name of this store. This is unique to this specific instance. * @@ -223,15 +210,6 @@ public StoreType getType() { return StoreType.valueOf(this.type); } - /** - * Sets the store type - * - * @param type the type - */ - public void setType(String type) { - this.type = type; - } - /** * Gets the configuration to this specific store. This is a map of strings. These options are * unique to the store. Please see protos/feast/core/Store.proto for the store specific @@ -257,57 +235,46 @@ public RedisStoreConfig getRedisConfig() { Boolean.valueOf(this.config.getOrDefault("ssl", "false")), this.config.getOrDefault("password", "")); } + } - /** - * Sets the store config. Please protos/feast/core/Store.proto for the specific options for each - * store. - * - * @param config the config map - */ - public void setConfig(Map config) { - this.config = config; + public static class Server { + private int port; + + public int getPort() { + return port; } } - public enum StoreType { - REDIS, - REDIS_CLUSTER; + public static class GrpcServer { + private Server server; + + public Server getServer() { + return server; + } } - /** - * Gets tracing properties - * - * @return tracing properties - */ - public TracingProperties getTracing() { - return tracing; + public static class RestServer { + private Server server; + + public Server getServer() { + return server; + } } - /** - * Sets the tracing configuration. - * - * @param tracing the tracing - */ - public void setTracing(TracingProperties tracing) { - this.tracing = tracing; + private GrpcServer grpc; + private RestServer rest; + + public GrpcServer getGrpc() { + return grpc; } - /** - * Gets logging properties - * - * @return logging properties - */ - public LoggingProperties getLogging() { - return logging; + public RestServer getRest() { + return rest; } - /** - * Sets logging properties - * - * @param logging the logging properties - */ - public void setLogging(LoggingProperties logging) { - this.logging = logging; + public enum StoreType { + REDIS, + REDIS_CLUSTER; } /** Trace metric collection properties */ @@ -387,7 +354,7 @@ public void validate() { Validator validator = factory.getValidator(); // Validate root fields in FeastProperties - Set> violations = validator.validate(this); + Set> violations = validator.validate(this); if (!violations.isEmpty()) { throw new ConstraintViolationException(violations); } diff --git a/java/serving/src/main/java/feast/serving/config/ApplicationPropertiesModule.java b/java/serving/src/main/java/feast/serving/config/ApplicationPropertiesModule.java new file mode 100644 index 0000000000..f5a542137c --- /dev/null +++ b/java/serving/src/main/java/feast/serving/config/ApplicationPropertiesModule.java @@ -0,0 +1,44 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2021 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.serving.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.google.inject.AbstractModule; +import com.google.inject.Provides; +import com.google.inject.Singleton; +import java.io.File; +import java.io.IOException; + +public class ApplicationPropertiesModule extends AbstractModule { + private final String[] args; + + public ApplicationPropertiesModule(String[] args) { + this.args = args; + } + + @Provides + @Singleton + public ApplicationProperties provideApplicationProperties() throws IOException { + ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + mapper.findAndRegisterModules(); + ApplicationProperties properties = + mapper.readValue(new File(this.args[0]), ApplicationProperties.class); + + return properties; + } +} diff --git a/java/serving/src/main/java/feast/serving/config/InstrumentationConfig.java b/java/serving/src/main/java/feast/serving/config/InstrumentationConfig.java index 295b263f66..7f8590bb84 100644 --- a/java/serving/src/main/java/feast/serving/config/InstrumentationConfig.java +++ b/java/serving/src/main/java/feast/serving/config/InstrumentationConfig.java @@ -16,47 +16,30 @@ */ package feast.serving.config; +import com.google.inject.AbstractModule; +import com.google.inject.Provides; import io.opentracing.Tracer; import io.opentracing.contrib.grpc.TracingServerInterceptor; import io.opentracing.noop.NoopTracerFactory; -import io.prometheus.client.exporter.MetricsServlet; -import io.prometheus.client.hotspot.DefaultExports; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.web.servlet.ServletRegistrationBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -@Configuration -public class InstrumentationConfig { +public class InstrumentationConfig extends AbstractModule { - private FeastProperties feastProperties; - - @Autowired - public InstrumentationConfig(FeastProperties feastProperties) { - this.feastProperties = feastProperties; - } - - @Bean - public ServletRegistrationBean servletRegistrationBean() { - DefaultExports.initialize(); - return new ServletRegistrationBean(new MetricsServlet(), "/metrics"); - } - - @Bean - public Tracer tracer() { - if (!feastProperties.getTracing().isEnabled()) { + @Provides + public Tracer tracer(ApplicationProperties applicationProperties) { + if (!applicationProperties.getFeast().getTracing().isEnabled()) { return NoopTracerFactory.create(); } - if (!feastProperties.getTracing().getTracerName().equalsIgnoreCase("jaeger")) { + if (!applicationProperties.getFeast().getTracing().getTracerName().equalsIgnoreCase("jaeger")) { throw new IllegalArgumentException("Only 'jaeger' tracer is supported for now."); } - return io.jaegertracing.Configuration.fromEnv(feastProperties.getTracing().getServiceName()) + return io.jaegertracing.Configuration.fromEnv( + applicationProperties.getFeast().getTracing().getServiceName()) .getTracer(); } - @Bean + @Provides public TracingServerInterceptor tracingInterceptor(Tracer tracer) { return TracingServerInterceptor.newBuilder().withTracer(tracer).build(); } diff --git a/java/serving/src/main/java/feast/serving/config/RegistryConfig.java b/java/serving/src/main/java/feast/serving/config/RegistryConfig.java index 38333dba16..d23ab374d8 100644 --- a/java/serving/src/main/java/feast/serving/config/RegistryConfig.java +++ b/java/serving/src/main/java/feast/serving/config/RegistryConfig.java @@ -20,42 +20,43 @@ import com.amazonaws.services.s3.AmazonS3ClientBuilder; import com.google.cloud.storage.Storage; import com.google.cloud.storage.StorageOptions; +import com.google.inject.AbstractModule; +import com.google.inject.Provider; +import com.google.inject.Provides; import feast.serving.registry.*; import java.net.URI; import java.util.Optional; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Lazy; -@Configuration -public class RegistryConfig { - @Bean - @Lazy - Storage googleStorage(FeastProperties feastProperties) { +public class RegistryConfig extends AbstractModule { + @Provides + Storage googleStorage(ApplicationProperties applicationProperties) { return StorageOptions.newBuilder() - .setProjectId(feastProperties.getGcpProject()) + .setProjectId(applicationProperties.getGcpProject()) .build() .getService(); } - @Bean - @Lazy - AmazonS3 awsStorage(FeastProperties feastProperties) { - return AmazonS3ClientBuilder.standard().withRegion(feastProperties.getAwsRegion()).build(); + @Provides + public AmazonS3 awsStorage(ApplicationProperties applicationProperties) { + return AmazonS3ClientBuilder.standard() + .withRegion(applicationProperties.getAwsRegion()) + .build(); } - @Bean - RegistryFile registryFile(FeastProperties feastProperties, ApplicationContext context) { + @Provides + RegistryFile registryFile( + ApplicationProperties applicationProperties, + Provider storageProvider, + Provider amazonS3Provider) { - String registryPath = feastProperties.getRegistry(); + String registryPath = applicationProperties.getFeast().getRegistry(); Optional scheme = Optional.ofNullable(URI.create(registryPath).getScheme()); - switch (scheme.orElseGet(() -> "")) { + switch (scheme.orElse("")) { case "gs": - return new GSRegistryFile(context.getBean(Storage.class), registryPath); + return new GSRegistryFile(storageProvider.get(), registryPath); case "s3": - return new S3RegistryFile(context.getBean(AmazonS3.class), registryPath); + return new S3RegistryFile(amazonS3Provider.get(), registryPath); case "": case "file": return new LocalRegistryFile(registryPath); @@ -64,9 +65,10 @@ RegistryFile registryFile(FeastProperties feastProperties, ApplicationContext co } } - @Bean + @Provides RegistryRepository registryRepository( - RegistryFile registryFile, FeastProperties feastProperties) { - return new RegistryRepository(registryFile, feastProperties.getRegistryRefreshInterval()); + RegistryFile registryFile, ApplicationProperties applicationProperties) { + return new RegistryRepository( + registryFile, applicationProperties.getFeast().getRegistryRefreshInterval()); } } diff --git a/java/serving/src/main/java/feast/serving/config/ServerModule.java b/java/serving/src/main/java/feast/serving/config/ServerModule.java new file mode 100644 index 0000000000..6993857935 --- /dev/null +++ b/java/serving/src/main/java/feast/serving/config/ServerModule.java @@ -0,0 +1,46 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2021 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.serving.config; + +import com.google.inject.AbstractModule; +import feast.serving.grpc.OnlineServingGrpcServiceV2; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.protobuf.services.ProtoReflectionService; +import io.opentracing.contrib.grpc.TracingServerInterceptor; + +public class ServerModule extends AbstractModule { + + @Override + protected void configure() { + bind(OnlineServingGrpcServiceV2.class); + } + + // @Provides + public Server provideGrpcServer( + ApplicationProperties applicationProperties, + OnlineServingGrpcServiceV2 onlineServingGrpcServiceV2, + TracingServerInterceptor tracingServerInterceptor) { + ServerBuilder serverBuilder = + ServerBuilder.forPort(applicationProperties.getGrpc().getServer().getPort()); + serverBuilder + .addService(ProtoReflectionService.newInstance()) + .addService(tracingServerInterceptor.intercept(onlineServingGrpcServiceV2)); + + return serverBuilder.build(); + } +} diff --git a/java/serving/src/main/java/feast/serving/config/ServingServiceConfigV2.java b/java/serving/src/main/java/feast/serving/config/ServingServiceConfigV2.java index 44a78faf20..52d7d1c8d6 100644 --- a/java/serving/src/main/java/feast/serving/config/ServingServiceConfigV2.java +++ b/java/serving/src/main/java/feast/serving/config/ServingServiceConfigV2.java @@ -16,6 +16,8 @@ */ package feast.serving.config; +import com.google.inject.AbstractModule; +import com.google.inject.Provides; import feast.serving.registry.*; import feast.serving.service.OnlineServingServiceV2; import feast.serving.service.OnlineTransformationService; @@ -24,18 +26,17 @@ import feast.storage.connectors.redis.retriever.*; import io.opentracing.Tracer; import org.slf4j.Logger; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -@Configuration -public class ServingServiceConfigV2 { +public class ServingServiceConfigV2 extends AbstractModule { private static final Logger log = org.slf4j.LoggerFactory.getLogger(ServingServiceConfigV2.class); - @Bean + @Provides public ServingServiceV2 registryBasedServingServiceV2( - FeastProperties feastProperties, RegistryRepository registryRepository, Tracer tracer) { + ApplicationProperties applicationProperties, + RegistryRepository registryRepository, + Tracer tracer) { final ServingServiceV2 servingService; - final FeastProperties.Store store = feastProperties.getActiveStore(); + final ApplicationProperties.Store store = applicationProperties.getFeast().getActiveStore(); OnlineRetrieverV2 retrieverV2; // TODO: Support more store types, and potentially use a plugin model here. @@ -59,7 +60,8 @@ public ServingServiceV2 registryBasedServingServiceV2( log.info("Working Directory = " + System.getProperty("user.dir")); - final String transformationServiceEndpoint = feastProperties.getTransformationServiceEndpoint(); + final String transformationServiceEndpoint = + applicationProperties.getTransformationServiceEndpoint(); final OnlineTransformationService onlineTransformationService = new OnlineTransformationService(transformationServiceEndpoint, registryRepository); diff --git a/java/serving/src/main/java/feast/serving/controller/ServingServiceGRpcController.java b/java/serving/src/main/java/feast/serving/controller/ServingServiceGRpcController.java index 8a56cc6d7b..0a406930e6 100644 --- a/java/serving/src/main/java/feast/serving/controller/ServingServiceGRpcController.java +++ b/java/serving/src/main/java/feast/serving/controller/ServingServiceGRpcController.java @@ -16,33 +16,22 @@ */ package feast.serving.controller; -import feast.common.logging.interceptors.GrpcMessageInterceptor; import feast.proto.serving.ServingAPIProto; import feast.proto.serving.ServingAPIProto.GetFeastServingInfoRequest; import feast.proto.serving.ServingAPIProto.GetFeastServingInfoResponse; import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse; import feast.proto.serving.ServingServiceGrpc.ServingServiceImplBase; -import feast.serving.config.FeastProperties; +import feast.serving.config.ApplicationProperties; import feast.serving.exception.SpecRetrievalException; import feast.serving.interceptors.GrpcMonitoringContext; -import feast.serving.interceptors.GrpcMonitoringInterceptor; import feast.serving.service.ServingServiceV2; import feast.serving.util.RequestHelper; import io.grpc.Status; import io.grpc.stub.StreamObserver; import io.opentracing.Span; import io.opentracing.Tracer; -import io.opentracing.contrib.grpc.TracingServerInterceptor; -import net.devh.boot.grpc.server.service.GrpcService; import org.slf4j.Logger; -import org.springframework.beans.factory.annotation.Autowired; -@GrpcService( - interceptors = { - TracingServerInterceptor.class, - GrpcMessageInterceptor.class, - GrpcMonitoringInterceptor.class - }) public class ServingServiceGRpcController extends ServingServiceImplBase { private static final Logger log = @@ -51,11 +40,12 @@ public class ServingServiceGRpcController extends ServingServiceImplBase { private final String version; private final Tracer tracer; - @Autowired public ServingServiceGRpcController( - ServingServiceV2 servingServiceV2, FeastProperties feastProperties, Tracer tracer) { + ServingServiceV2 servingServiceV2, + ApplicationProperties applicationProperties, + Tracer tracer) { this.servingServiceV2 = servingServiceV2; - this.version = feastProperties.getVersion(); + this.version = applicationProperties.getFeast().getVersion(); this.tracer = tracer; } @@ -75,7 +65,8 @@ public void getOnlineFeaturesV2( StreamObserver responseObserver) { try { // authorize for the project in request object. - if (request.getProject() != null && !request.getProject().isEmpty()) { + request.getProject(); + if (!request.getProject().isEmpty()) { // update monitoring context GrpcMonitoringContext.getInstance().setProject(request.getProject()); } diff --git a/java/serving/src/main/java/feast/serving/controller/ServingServiceRestController.java b/java/serving/src/main/java/feast/serving/controller/ServingServiceRestController.java index 8a198a8201..2f446adf67 100644 --- a/java/serving/src/main/java/feast/serving/controller/ServingServiceRestController.java +++ b/java/serving/src/main/java/feast/serving/controller/ServingServiceRestController.java @@ -22,27 +22,23 @@ import feast.proto.serving.ServingAPIProto.GetFeastServingInfoResponse; import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequestV2; import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse; -import feast.serving.config.FeastProperties; +import feast.serving.config.ApplicationProperties; import feast.serving.service.ServingServiceV2; import feast.serving.util.RequestHelper; import java.util.List; import java.util.Map; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -@RestController public class ServingServiceRestController { private final ServingServiceV2 servingService; private final String version; - @Autowired public ServingServiceRestController( - ServingServiceV2 servingService, FeastProperties feastProperties) { + ServingServiceV2 servingService, ApplicationProperties applicationProperties) { this.servingService = servingService; - this.version = feastProperties.getVersion(); + this.version = applicationProperties.getFeast().getVersion(); } @RequestMapping(value = "/api/v1/info", produces = "application/json") diff --git a/java/serving/src/main/java/feast/serving/grpc/OnlineServingGrpcServiceV2.java b/java/serving/src/main/java/feast/serving/grpc/OnlineServingGrpcServiceV2.java new file mode 100644 index 0000000000..68a17539ab --- /dev/null +++ b/java/serving/src/main/java/feast/serving/grpc/OnlineServingGrpcServiceV2.java @@ -0,0 +1,48 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2021 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.serving.grpc; + +import feast.proto.serving.ServingAPIProto; +import feast.proto.serving.ServingServiceGrpc; +import feast.serving.service.ServingServiceV2; +import io.grpc.stub.StreamObserver; +import javax.inject.Inject; + +public class OnlineServingGrpcServiceV2 extends ServingServiceGrpc.ServingServiceImplBase { + private final ServingServiceV2 servingServiceV2; + + @Inject + OnlineServingGrpcServiceV2(ServingServiceV2 servingServiceV2) { + this.servingServiceV2 = servingServiceV2; + } + + @Override + public void getFeastServingInfo( + ServingAPIProto.GetFeastServingInfoRequest request, + StreamObserver responseObserver) { + responseObserver.onNext(this.servingServiceV2.getFeastServingInfo(request)); + responseObserver.onCompleted(); + } + + @Override + public void getOnlineFeaturesV2( + ServingAPIProto.GetOnlineFeaturesRequestV2 request, + StreamObserver responseObserver) { + responseObserver.onNext(this.servingServiceV2.getOnlineFeatures(request)); + responseObserver.onCompleted(); + } +} diff --git a/java/serving/src/main/java/feast/serving/modules/ServerModule.java b/java/serving/src/main/java/feast/serving/modules/ServerModule.java new file mode 100644 index 0000000000..29d1f57432 --- /dev/null +++ b/java/serving/src/main/java/feast/serving/modules/ServerModule.java @@ -0,0 +1,19 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2021 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.serving.modules; + +public class ServerModule {} diff --git a/java/serving/src/main/java/feast/serving/registry/RegistryRepository.java b/java/serving/src/main/java/feast/serving/registry/RegistryRepository.java index c043238906..23c204b582 100644 --- a/java/serving/src/main/java/feast/serving/registry/RegistryRepository.java +++ b/java/serving/src/main/java/feast/serving/registry/RegistryRepository.java @@ -53,7 +53,12 @@ public RegistryRepository(Registry registry) { } private void setupPeriodicalRefresh(int seconds) { - Executors.newSingleThreadScheduledExecutor() + Executors.newSingleThreadScheduledExecutor( + r -> { + Thread t = Executors.defaultThreadFactory().newThread(r); + t.setDaemon(true); + return t; + }) .scheduleWithFixedDelay(this::refresh, seconds, seconds, TimeUnit.SECONDS); } diff --git a/java/serving/src/main/resources/application.yml b/java/serving/src/main/resources/application.yml index 9da20d1e44..4fba32a0ae 100644 --- a/java/serving/src/main/resources/application.yml +++ b/java/serving/src/main/resources/application.yml @@ -1,10 +1,10 @@ feast: - registry: "" - registry-refresh-interval: 0 + registry: "prompt_dory/data/registry.db" + registryRefreshInterval: 0 # Indicates the active store. Only a single store in the last can be active at one time. In the future this key # will be deprecated in order to allow multiple stores to be served from a single serving instance - active_store: online + activeStore: online # List of store configurations stores: @@ -29,9 +29,9 @@ feast: enabled: false # Only Jaeger tracer is supported currently # https://opentracing.io/docs/supported-tracers/ - tracer-name: jaeger + tracerName: jaeger # The service name identifier for the tracing data - service-name: feast_serving + serviceName: feast_serving logging: # Audit logging provides a machine readable structured JSON log that can give better @@ -54,14 +54,11 @@ grpc: # The port number Feast Serving GRPC service should listen on # 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} - security: - enabled: false - certificateChain: server.crt - privateKey: server.key + port: 6566 -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 - port: ${SERVER_PORT:8081} +rest: + 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 + port: 8081 diff --git a/java/serving/src/test/java/feast/serving/controller/ServingServiceGRpcControllerTest.java b/java/serving/src/test/java/feast/serving/controller/ServingServiceGRpcControllerTest.java deleted file mode 100644 index c8a41bccc7..0000000000 --- a/java/serving/src/test/java/feast/serving/controller/ServingServiceGRpcControllerTest.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright 2018-2019 The Feast Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package feast.serving.controller; - -import static org.mockito.MockitoAnnotations.initMocks; - -import com.google.protobuf.Timestamp; -import feast.proto.serving.ServingAPIProto.FeatureReferenceV2; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequestV2; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequestV2.EntityRow; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse; -import feast.proto.types.ValueProto.Value; -import feast.serving.config.FeastProperties; -import feast.serving.service.ServingServiceV2; -import io.grpc.StatusRuntimeException; -import io.grpc.stub.StreamObserver; -import io.jaegertracing.Configuration; -import io.opentracing.Tracer; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.springframework.security.core.Authentication; - -public class ServingServiceGRpcControllerTest { - - @Mock private ServingServiceV2 mockServingServiceV2; - - @Mock private StreamObserver mockStreamObserver; - - private GetOnlineFeaturesRequestV2 validRequest; - - private ServingServiceGRpcController service; - - @Mock private Authentication authentication; - - @Before - public void setUp() { - initMocks(this); - - validRequest = - GetOnlineFeaturesRequestV2.newBuilder() - .addFeatures( - FeatureReferenceV2.newBuilder() - .setFeatureTable("featuretable_1") - .setName("feature1") - .build()) - .addFeatures( - FeatureReferenceV2.newBuilder() - .setFeatureTable("featuretable_1") - .setName("feature2") - .build()) - .addEntityRows( - EntityRow.newBuilder() - .setTimestamp(Timestamp.newBuilder().setSeconds(100)) - .putFields("entity1", Value.newBuilder().setInt64Val(1).build()) - .putFields("entity2", Value.newBuilder().setInt64Val(1).build())) - .build(); - } - - private ServingServiceGRpcController getServingServiceGRpcController(boolean enableAuth) { - Tracer tracer = Configuration.fromEnv("dummy").getTracer(); - FeastProperties feastProperties = new FeastProperties(); - - return new ServingServiceGRpcController(mockServingServiceV2, feastProperties, tracer); - } - - @Test - public void shouldPassValidRequestAsIs() { - service = getServingServiceGRpcController(false); - service.getOnlineFeaturesV2(validRequest, mockStreamObserver); - Mockito.verify(mockServingServiceV2).getOnlineFeatures(validRequest); - } - - @Test - public void shouldCallOnErrorIfEntityDatasetIsNotSet() { - service = getServingServiceGRpcController(false); - GetOnlineFeaturesRequestV2 missingEntityName = - GetOnlineFeaturesRequestV2.newBuilder(validRequest).clearEntityRows().build(); - service.getOnlineFeaturesV2(missingEntityName, mockStreamObserver); - Mockito.verify(mockStreamObserver).onError(Mockito.any(StatusRuntimeException.class)); - } -} diff --git a/java/serving/src/test/java/feast/serving/it/ServingBase.java b/java/serving/src/test/java/feast/serving/it/ServingBase.java index 27da6e9771..3a42f9a85e 100644 --- a/java/serving/src/test/java/feast/serving/it/ServingBase.java +++ b/java/serving/src/test/java/feast/serving/it/ServingBase.java @@ -18,10 +18,12 @@ import static org.awaitility.Awaitility.await; import static org.hamcrest.Matchers.equalTo; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; import com.google.common.collect.ImmutableList; +import com.google.inject.*; +import com.google.inject.Module; +import com.google.inject.util.Modules; import com.google.protobuf.Timestamp; import feast.proto.core.FeatureProto; import feast.proto.core.FeatureViewProto; @@ -29,49 +31,38 @@ import feast.proto.serving.ServingAPIProto; import feast.proto.serving.ServingServiceGrpc; import feast.proto.types.ValueProto; +import feast.serving.config.*; +import feast.serving.grpc.OnlineServingGrpcServiceV2; import feast.serving.util.DataGenerator; -import io.grpc.ManagedChannel; -import io.grpc.StatusRuntimeException; +import io.grpc.*; +import io.grpc.inprocess.InProcessChannelBuilder; +import io.grpc.inprocess.InProcessServerBuilder; +import io.grpc.protobuf.services.ProtoReflectionService; +import io.grpc.util.MutableHandlerRegistry; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.time.Duration; import java.util.concurrent.TimeUnit; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.DynamicPropertyRegistry; -import org.springframework.test.context.DynamicPropertySource; +import org.junit.jupiter.api.*; import org.testcontainers.containers.DockerComposeContainer; import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.junit.jupiter.Testcontainers; -@ActiveProfiles("it") @Testcontainers -@SpringBootTest -@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) abstract class ServingBase { - static ServingServiceGrpc.ServingServiceBlockingStub servingStub; - - static final int FEAST_SERVING_PORT = 6568; - - @DynamicPropertySource - static void initialize(DynamicPropertyRegistry registry) { - registry.add("grpc.server.port", () -> FEAST_SERVING_PORT); - - registry.add("feast.stores[0].name", () -> "online"); - registry.add("feast.stores[0].type", () -> "REDIS"); - registry.add("feast.stores[0].config.host", () -> environment.getServiceHost("redis", 6379)); - registry.add("feast.stores[0].config.port", () -> environment.getServicePort("redis", 6379)); - } - static DockerComposeContainer environment; - static { + ServingServiceGrpc.ServingServiceBlockingStub servingStub; + Injector injector; + String serverName; + ManagedChannel channel; + Server server; + MutableHandlerRegistry serviceRegistry; + + @BeforeAll + static void globalSetup() { environment = new DockerComposeContainer( new File("src/test/resources/docker-compose/docker-compose-redis-it.yml")) @@ -84,14 +75,88 @@ static void initialize(DynamicPropertyRegistry registry) { environment.start(); } - @BeforeAll - static void globalSetup() { - servingStub = TestUtils.getServingServiceStub(false, FEAST_SERVING_PORT, null); + @AfterAll + static void globalTeardown() { + environment.stop(); } - @AfterAll - static void tearDown() throws Exception { - ((ManagedChannel) servingStub.getChannel()).shutdown().awaitTermination(10, TimeUnit.SECONDS); + @BeforeEach + public void envSetUp() throws Exception { + + AbstractModule appPropertiesModule = + new AbstractModule() { + @Override + protected void configure() { + bind(OnlineServingGrpcServiceV2.class); + } + + @Provides + ApplicationProperties applicationProperties() { + final ApplicationProperties p = new ApplicationProperties(); + p.setAwsRegion("us-east-1"); + + final ApplicationProperties.FeastProperties feastProperties = createFeastProperties(); + p.setFeast(feastProperties); + + final ApplicationProperties.TracingProperties tracingProperties = + new ApplicationProperties.TracingProperties(); + feastProperties.setTracing(tracingProperties); + + tracingProperties.setEnabled(false); + return p; + } + }; + + Module overrideConfig = registryConfig(); + Module registryConfig; + if (overrideConfig != null) { + registryConfig = Modules.override(new RegistryConfig()).with(registryConfig()); + } else { + registryConfig = new RegistryConfig(); + } + + injector = + Guice.createInjector( + new ServingServiceConfigV2(), + registryConfig, + new InstrumentationConfig(), + appPropertiesModule); + + OnlineServingGrpcServiceV2 onlineServingGrpcServiceV2 = + injector.getInstance(OnlineServingGrpcServiceV2.class); + + serverName = InProcessServerBuilder.generateName(); + + server = + InProcessServerBuilder.forName(serverName) + .fallbackHandlerRegistry(serviceRegistry) + .addService(onlineServingGrpcServiceV2) + .addService(ProtoReflectionService.newInstance()) + .build(); + server.start(); + + channel = InProcessChannelBuilder.forName(serverName).usePlaintext().directExecutor().build(); + + servingStub = + ServingServiceGrpc.newBlockingStub(channel) + .withDeadlineAfter(5, TimeUnit.SECONDS) + .withWaitForReady(); + } + + @AfterEach + public void envTeardown() throws Exception { + // assume channel and server are not null + channel.shutdown(); + server.shutdown(); + // fail the test if cannot gracefully shutdown + try { + assert channel.awaitTermination(5, TimeUnit.SECONDS) + : "channel cannot be gracefully shutdown"; + assert server.awaitTermination(5, TimeUnit.SECONDS) : "server cannot be gracefully shutdown"; + } finally { + channel.shutdownNow(); + server.shutdownNow(); + } } protected ServingAPIProto.GetOnlineFeaturesRequestV2 buildOnlineRequest(int driverId) { @@ -134,8 +199,9 @@ private static RegistryProto.Registry readLocalRegistry() { @Test public void shouldGetOnlineFeatures() { + ServingAPIProto.GetOnlineFeaturesRequestV2 req = buildOnlineRequest(1005); ServingAPIProto.GetOnlineFeaturesResponse featureResponse = - servingStub.getOnlineFeaturesV2(buildOnlineRequest(1005)); + servingStub.withDeadlineAfter(1000, TimeUnit.MILLISECONDS).getOnlineFeaturesV2(req); assertEquals(1, featureResponse.getFieldValuesCount()); @@ -159,7 +225,6 @@ public void shouldGetOnlineFeatures() { } @Test - @DirtiesContext(methodMode = DirtiesContext.MethodMode.AFTER_METHOD) public void shouldGetOnlineFeaturesWithOutsideMaxAgeStatus() { ServingAPIProto.GetOnlineFeaturesResponse featureResponse = servingStub.getOnlineFeaturesV2(buildOnlineRequest(1001)); @@ -232,5 +297,11 @@ public void shouldRefreshRegistryAndServeNewFeatures() throws InterruptedExcepti .until(() -> servingStub.getOnlineFeaturesV2(request).getFieldValuesCount(), equalTo(1)); } + abstract ApplicationProperties.FeastProperties createFeastProperties(); + + AbstractModule registryConfig() { + return null; + } + abstract void updateRegistryFile(RegistryProto.Registry registry); } diff --git a/java/serving/src/test/java/feast/serving/it/ServingRedisGSRegistryIT.java b/java/serving/src/test/java/feast/serving/it/ServingRedisGSRegistryIT.java index db8b7f0759..36e0eebe8d 100644 --- a/java/serving/src/test/java/feast/serving/it/ServingRedisGSRegistryIT.java +++ b/java/serving/src/test/java/feast/serving/it/ServingRedisGSRegistryIT.java @@ -20,15 +20,14 @@ import com.google.cloud.storage.*; import com.google.cloud.storage.testing.RemoteStorageHelper; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import feast.proto.core.RegistryProto; +import feast.serving.config.ApplicationProperties; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.test.context.DynamicPropertyRegistry; -import org.springframework.test.context.DynamicPropertySource; public class ServingRedisGSRegistryIT extends ServingBase { static Storage storage = @@ -41,26 +40,20 @@ public class ServingRedisGSRegistryIT extends ServingBase { static final String bucket = RemoteStorageHelper.generateBucketName(); - @DynamicPropertySource - static void initialize(DynamicPropertyRegistry registry) { - registry.add("feast.registry", () -> String.format("gs://%s/registry.db", bucket)); - registry.add("feast.registry-refresh-interval", () -> 1); - - ServingBase.initialize(registry); - } - - static void putToStorage(RegistryProto.Registry registry) { - BlobId blobId = BlobId.of(bucket, "registry.db"); + static void putToStorage(BlobId blobId, RegistryProto.Registry registry) { storage.create(BlobInfo.newBuilder(blobId).build(), registry.toByteArray()); assertArrayEquals(storage.get(blobId).getContent(), registry.toByteArray()); } + static BlobId blobId; + @BeforeAll static void setUp() { storage.create(BucketInfo.of(bucket)); + blobId = BlobId.of(bucket, "registry.db"); - putToStorage(registryProto); + putToStorage(blobId, registryProto); } @AfterAll @@ -69,15 +62,24 @@ static void tearDown() throws ExecutionException, InterruptedException { } @Override - void updateRegistryFile(RegistryProto.Registry registry) { - putToStorage(registry); + ApplicationProperties.FeastProperties createFeastProperties() { + final ApplicationProperties.FeastProperties feastProperties = + new ApplicationProperties.FeastProperties(); + feastProperties.setRegistry(blobId.toGsUtilUri()); + feastProperties.setRegistryRefreshInterval(1); + + feastProperties.setActiveStore("online"); + + feastProperties.setStores( + ImmutableList.of( + new ApplicationProperties.Store( + "online", "REDIS", ImmutableMap.of("host", "localhost", "port", "6379")))); + + return feastProperties; } - @TestConfiguration - public static class GSRegistryConfig { - @Bean - Storage googleStorage() { - return storage; - } + @Override + void updateRegistryFile(RegistryProto.Registry registry) { + putToStorage(blobId, registry); } } diff --git a/java/serving/src/test/java/feast/serving/it/ServingRedisLocalRegistryIT.java b/java/serving/src/test/java/feast/serving/it/ServingRedisLocalRegistryIT.java index 074506432f..53fda39466 100644 --- a/java/serving/src/test/java/feast/serving/it/ServingRedisLocalRegistryIT.java +++ b/java/serving/src/test/java/feast/serving/it/ServingRedisLocalRegistryIT.java @@ -16,47 +16,32 @@ */ package feast.serving.it; -import static org.junit.jupiter.api.Assertions.*; - -import com.squareup.okhttp.OkHttpClient; -import com.squareup.okhttp.Request; -import com.squareup.okhttp.Response; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import feast.proto.core.RegistryProto; -import java.io.IOException; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.web.server.LocalServerPort; +import feast.serving.config.ApplicationProperties; -@SpringBootTest( - webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - properties = { - "feast.registry:src/test/resources/docker-compose/feast10/registry.db", - }) public class ServingRedisLocalRegistryIT extends ServingBase { + @Override + ApplicationProperties.FeastProperties createFeastProperties() { + final ApplicationProperties.FeastProperties feastProperties = + new ApplicationProperties.FeastProperties(); + feastProperties.setRegistry("src/test/resources/docker-compose/feast10/registry.db"); + feastProperties.setRegistryRefreshInterval(1); + + feastProperties.setActiveStore("online"); - public static final Logger log = LoggerFactory.getLogger(ServingRedisLocalRegistryIT.class); + feastProperties.setStores( + ImmutableList.of( + new ApplicationProperties.Store( + "online", "REDIS", ImmutableMap.of("host", "localhost", "port", "6379")))); - @LocalServerPort private int metricsPort; + return feastProperties; + } @Override void updateRegistryFile(RegistryProto.Registry registry) {} - @Disabled @Override public void shouldRefreshRegistryAndServeNewFeatures() throws InterruptedException {} - - @Test - public void shouldAllowUnauthenticatedAccessToMetricsEndpoint() throws IOException { - Request request = - new Request.Builder() - .url(String.format("http://localhost:%d/metrics", metricsPort)) - .get() - .build(); - Response response = new OkHttpClient().newCall(request).execute(); - assertTrue(response.isSuccessful()); - assertFalse(response.body().string().isEmpty()); - } } diff --git a/java/serving/src/test/java/feast/serving/it/ServingRedisS3RegistryIT.java b/java/serving/src/test/java/feast/serving/it/ServingRedisS3RegistryIT.java index 109ba6a5a9..648fdaa5b5 100644 --- a/java/serving/src/test/java/feast/serving/it/ServingRedisS3RegistryIT.java +++ b/java/serving/src/test/java/feast/serving/it/ServingRedisS3RegistryIT.java @@ -21,18 +21,18 @@ import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3ClientBuilder; import com.amazonaws.services.s3.model.ObjectMetadata; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.inject.AbstractModule; +import com.google.inject.Provides; import feast.proto.core.RegistryProto; +import feast.serving.config.ApplicationProperties; import java.io.ByteArrayInputStream; -import java.io.IOException; import org.junit.jupiter.api.BeforeAll; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.test.context.DynamicPropertyRegistry; -import org.springframework.test.context.DynamicPropertySource; import org.testcontainers.junit.jupiter.Container; public class ServingRedisS3RegistryIT extends ServingBase { - @Container private static final S3MockContainer s3Mock = new S3MockContainer("2.2.3"); + @Container static final S3MockContainer s3Mock = new S3MockContainer("2.2.3"); private static AmazonS3 createClient() { return AmazonS3ClientBuilder.standard() @@ -43,14 +43,6 @@ private static AmazonS3 createClient() { .build(); } - @DynamicPropertySource - static void initialize(DynamicPropertyRegistry registry) { - registry.add("feast.registry", () -> "s3://test-bucket/registry.db"); - registry.add("feast.registry-refresh-interval", () -> 1); - - ServingBase.initialize(registry); - } - private static void putToStorage(RegistryProto.Registry proto) { byte[] bytes = proto.toByteArray(); ObjectMetadata metadata = new ObjectMetadata(); @@ -62,23 +54,47 @@ private static void putToStorage(RegistryProto.Registry proto) { } @BeforeAll - static void setUp() throws IOException { + static void setUp() { AmazonS3 s3Client = createClient(); s3Client.createBucket("test-bucket"); putToStorage(registryProto); } + @Override + ApplicationProperties.FeastProperties createFeastProperties() { + final ApplicationProperties.FeastProperties feastProperties = + new ApplicationProperties.FeastProperties(); + feastProperties.setRegistry("s3://test-bucket/registry.db"); + feastProperties.setRegistryRefreshInterval(1); + + feastProperties.setActiveStore("online"); + + feastProperties.setStores( + ImmutableList.of( + new ApplicationProperties.Store( + "online", "REDIS", ImmutableMap.of("host", "localhost", "port", "6379")))); + + return feastProperties; + } + @Override void updateRegistryFile(RegistryProto.Registry registry) { putToStorage(registry); } - @TestConfiguration - public static class S3RegistryConfig { - @Bean - AmazonS3 awsStorage() { - return createClient(); - } + @Override + AbstractModule registryConfig() { + return new AbstractModule() { + @Provides + public AmazonS3 awsStorage() { + return AmazonS3ClientBuilder.standard() + .withEndpointConfiguration( + new AwsClientBuilder.EndpointConfiguration( + String.format("http://localhost:%d", s3Mock.getHttpServerPort()), "us-east-1")) + .enablePathStyleAccess() + .build(); + } + }; } }