Skip to content

Commit 2461ac1

Browse files
committed
Authentication integration test.
1 parent f5fdb3f commit 2461ac1

9 files changed

Lines changed: 621 additions & 0 deletions

File tree

serving/pom.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,24 @@
316316
<artifactId>spring-security-oauth2-core</artifactId>
317317
<version>${spring.security.version}</version>
318318
</dependency>
319+
<dependency>
320+
<groupId>org.testcontainers</groupId>
321+
<artifactId>testcontainers</artifactId>
322+
<version>1.14.3</version>
323+
<scope>test</scope>
324+
</dependency>
325+
<dependency>
326+
<groupId>org.testcontainers</groupId>
327+
<artifactId>junit-jupiter</artifactId>
328+
<version>1.14.3</version>
329+
<scope>test</scope>
330+
</dependency>
331+
<dependency>
332+
<groupId>org.awaitility</groupId>
333+
<artifactId>awaitility</artifactId>
334+
<version>3.0.0</version>
335+
<scope>test</scope>
336+
</dependency>
319337
</dependencies>
320338

321339
<profiles>
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright 2018-2020 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.serving.it;
18+
19+
import static org.awaitility.Awaitility.waitAtMost;
20+
import static org.hamcrest.CoreMatchers.equalTo;
21+
import static org.hamcrest.beans.HasPropertyWithValue.hasProperty;
22+
import static org.junit.jupiter.api.Assertions.assertEquals;
23+
24+
import com.google.gson.JsonArray;
25+
import com.google.gson.JsonObject;
26+
import com.google.protobuf.Timestamp;
27+
import feast.auth.credentials.OAuthCredentials;
28+
import feast.proto.core.CoreServiceGrpc;
29+
import feast.proto.core.FeatureSetProto;
30+
import feast.proto.core.FeatureSetProto.FeatureSetStatus;
31+
import feast.proto.core.SourceProto;
32+
import feast.proto.serving.ServingAPIProto.FeatureReference;
33+
import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequest;
34+
import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequest.EntityRow;
35+
import feast.proto.serving.ServingServiceGrpc;
36+
import feast.proto.types.ValueProto;
37+
import feast.proto.types.ValueProto.Value;
38+
import io.grpc.CallCredentials;
39+
import io.grpc.Channel;
40+
import io.grpc.ManagedChannelBuilder;
41+
import java.io.IOException;
42+
import java.util.ArrayList;
43+
import java.util.List;
44+
import java.util.Map;
45+
import java.util.concurrent.TimeUnit;
46+
import java.util.stream.Collectors;
47+
import okhttp3.MediaType;
48+
import okhttp3.OkHttpClient;
49+
import okhttp3.Request;
50+
import okhttp3.RequestBody;
51+
import okhttp3.Response;
52+
import org.apache.commons.lang3.tuple.Pair;
53+
import org.junit.runners.model.InitializationError;
54+
55+
public class AuthTestUtils {
56+
57+
static SourceProto.Source defaultSource =
58+
createSource("kafka:9092,localhost:9094", "feast-features");
59+
60+
public static SourceProto.Source getDefaultSource() {
61+
return defaultSource;
62+
}
63+
64+
public static SourceProto.Source createSource(String server, String topic) {
65+
return SourceProto.Source.newBuilder()
66+
.setType(SourceProto.SourceType.KAFKA)
67+
.setKafkaSourceConfig(
68+
SourceProto.KafkaSourceConfig.newBuilder()
69+
.setBootstrapServers(server)
70+
.setTopic(topic)
71+
.build())
72+
.build();
73+
}
74+
75+
public static FeatureSetProto.FeatureSet createFeatureSet(
76+
SourceProto.Source source,
77+
String projectName,
78+
String name,
79+
List<Pair<String, ValueProto.ValueType.Enum>> entities,
80+
List<Pair<String, ValueProto.ValueType.Enum>> features) {
81+
return FeatureSetProto.FeatureSet.newBuilder()
82+
.setSpec(
83+
FeatureSetProto.FeatureSetSpec.newBuilder()
84+
.setSource(source)
85+
.setName(name)
86+
.setProject(projectName)
87+
.addAllEntities(
88+
entities.stream()
89+
.map(
90+
pair ->
91+
FeatureSetProto.EntitySpec.newBuilder()
92+
.setName(pair.getLeft())
93+
.setValueType(pair.getRight())
94+
.build())
95+
.collect(Collectors.toList()))
96+
.addAllFeatures(
97+
features.stream()
98+
.map(
99+
pair ->
100+
FeatureSetProto.FeatureSpec.newBuilder()
101+
.setName(pair.getLeft())
102+
.setValueType(pair.getRight())
103+
.build())
104+
.collect(Collectors.toList()))
105+
.build())
106+
.build();
107+
}
108+
109+
public static GetOnlineFeaturesRequest createOnlineFeatureRequest(
110+
String projectName, String featureName, String entityId, int entityValue) {
111+
return GetOnlineFeaturesRequest.newBuilder()
112+
.setProject(projectName)
113+
.addFeatures(FeatureReference.newBuilder().setName(featureName).build())
114+
.addEntityRows(
115+
EntityRow.newBuilder()
116+
.setEntityTimestamp(Timestamp.newBuilder().setSeconds(100))
117+
.putFields(entityId, Value.newBuilder().setInt64Val(entityValue).build()))
118+
.build();
119+
}
120+
121+
public static void applyFeatureSet(
122+
CoreSimpleAPIClient secureApiClient,
123+
String projectName,
124+
String entityId,
125+
String featureName) {
126+
List<Pair<String, ValueProto.ValueType.Enum>> entities = new ArrayList<>();
127+
entities.add(Pair.of(entityId, ValueProto.ValueType.Enum.INT64));
128+
List<Pair<String, ValueProto.ValueType.Enum>> features = new ArrayList<>();
129+
features.add(Pair.of(featureName, ValueProto.ValueType.Enum.INT64));
130+
String featureSetName = "test_1";
131+
FeatureSetProto.FeatureSet expectedFeatureSet =
132+
AuthTestUtils.createFeatureSet(
133+
AuthTestUtils.getDefaultSource(), projectName, featureSetName, entities, features);
134+
secureApiClient.simpleApplyFeatureSet(expectedFeatureSet);
135+
waitAtMost(2, TimeUnit.MINUTES)
136+
.until(
137+
() -> {
138+
return secureApiClient.simpleGetFeatureSet(projectName, featureSetName).getMeta();
139+
},
140+
hasProperty("status", equalTo(FeatureSetStatus.STATUS_READY)));
141+
FeatureSetProto.FeatureSet actualFeatureSet =
142+
secureApiClient.simpleGetFeatureSet(projectName, featureSetName);
143+
assertEquals(
144+
expectedFeatureSet.getSpec().getProject(), actualFeatureSet.getSpec().getProject());
145+
assertEquals(expectedFeatureSet.getSpec().getName(), actualFeatureSet.getSpec().getName());
146+
assertEquals(expectedFeatureSet.getSpec().getSource(), actualFeatureSet.getSpec().getSource());
147+
assertEquals(FeatureSetStatus.STATUS_READY, actualFeatureSet.getMeta().getStatus());
148+
}
149+
150+
public static CoreSimpleAPIClient getSecureApiClientForCore(
151+
int feastCorePort, Map<String, String> options) {
152+
CallCredentials callCredentials = null;
153+
callCredentials = new OAuthCredentials(options);
154+
Channel secureChannel =
155+
ManagedChannelBuilder.forAddress("localhost", feastCorePort).usePlaintext().build();
156+
157+
CoreServiceGrpc.CoreServiceBlockingStub secureCoreService =
158+
CoreServiceGrpc.newBlockingStub(secureChannel).withCallCredentials(callCredentials);
159+
160+
return new CoreSimpleAPIClient(secureCoreService);
161+
}
162+
163+
public static ServingServiceGrpc.ServingServiceBlockingStub getServingServiceStub(
164+
boolean isSecure, int feastServingPort, Map<String, String> options) {
165+
Channel secureChannel =
166+
ManagedChannelBuilder.forAddress("localhost", feastServingPort).usePlaintext().build();
167+
168+
if (isSecure) {
169+
CallCredentials callCredentials = null;
170+
callCredentials = new OAuthCredentials(options);
171+
return ServingServiceGrpc.newBlockingStub(secureChannel).withCallCredentials(callCredentials);
172+
} else {
173+
return ServingServiceGrpc.newBlockingStub(secureChannel);
174+
}
175+
}
176+
177+
public static void seedHydra(
178+
String hydraExternalUrl,
179+
String clientId,
180+
String clientSecrret,
181+
String audience,
182+
String grantType)
183+
throws IOException, InitializationError {
184+
185+
OkHttpClient httpClient = new OkHttpClient();
186+
String createClientEndpoint = String.format("%s/%s", hydraExternalUrl, "clients");
187+
JsonObject jsonObject = new JsonObject();
188+
JsonArray audienceArrray = new JsonArray();
189+
audienceArrray.add(audience);
190+
JsonArray grantTypes = new JsonArray();
191+
grantTypes.add(grantType);
192+
jsonObject.addProperty("client_id", clientId);
193+
jsonObject.addProperty("client_secret", clientSecrret);
194+
jsonObject.addProperty("token_endpoint_auth_method", "client_secret_post");
195+
jsonObject.add("audience", audienceArrray);
196+
jsonObject.add("grant_types", grantTypes);
197+
MediaType JSON = MediaType.parse("application/json; charset=utf-8");
198+
199+
RequestBody requestBody = RequestBody.create(JSON, jsonObject.toString());
200+
Request request =
201+
new Request.Builder()
202+
.url(createClientEndpoint)
203+
.addHeader("Content-Type", "application/json")
204+
.post(requestBody)
205+
.build();
206+
Response response = httpClient.newCall(request).execute();
207+
if (!response.isSuccessful()) {
208+
throw new InitializationError(response.message());
209+
}
210+
}
211+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright 2018-2020 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.serving.it;
18+
19+
import java.net.InetAddress;
20+
import java.net.UnknownHostException;
21+
import java.util.HashMap;
22+
import java.util.Map;
23+
import org.springframework.boot.test.context.SpringBootTest;
24+
import org.springframework.test.context.ActiveProfiles;
25+
import org.springframework.test.context.DynamicPropertyRegistry;
26+
import org.springframework.test.context.DynamicPropertySource;
27+
28+
@ActiveProfiles("it")
29+
@SpringBootTest
30+
public class BaseAuthIT {
31+
32+
static final String FEATURE_NAME = "feature_1";
33+
static final String ENTITY_ID = "entity_id";
34+
static final String PROJECT_NAME = "project_1";
35+
static final int CORE_START_MAX_WAIT_TIME_IN_MINUTES = 3;
36+
static final String CLIENT_ID = "client_id";
37+
static final String CLIENT_SECRET = "client_secret";
38+
static final String TOKEN_URL = "http://localhost:4444/oauth2/token";
39+
static final String JWK_URI = "http://localhost:4444/.well-known/jwks.json";
40+
41+
static final String GRANT_TYPE = "client_credentials";
42+
43+
static final String AUDIENCE = "https://localhost";
44+
45+
static final String CORE = "core_1";
46+
47+
static final String HYDRA = "hydra_1";
48+
static final Map<String, String> options = new HashMap<>();
49+
static final int HYDRA_PORT = 4445;
50+
51+
static CoreSimpleAPIClient insecureApiClient;
52+
53+
static final int REDIS_PORT = 6379;
54+
55+
static final int FEAST_CORE_PORT = 6565;
56+
static final int FEAST_SERVING_PORT = 6566;
57+
58+
@DynamicPropertySource
59+
static void initialize(DynamicPropertyRegistry registry) throws UnknownHostException {
60+
registry.add("feast.stores[0].name", () -> "online");
61+
registry.add("feast.stores[0].type", () -> "REDIS");
62+
// Redis needs to accessible by both core and serving, hence using host address
63+
registry.add(
64+
"feast.stores[0].config.host",
65+
() -> {
66+
try {
67+
return InetAddress.getLocalHost().getHostAddress();
68+
} catch (UnknownHostException e) {
69+
// TODO Auto-generated catch block
70+
e.printStackTrace();
71+
return "";
72+
}
73+
});
74+
registry.add("feast.stores[0].config.port", () -> REDIS_PORT);
75+
registry.add("feast.stores[0].subscriptions[0].name", () -> "*");
76+
registry.add("feast.stores[0].subscriptions[0].project", () -> "*");
77+
78+
registry.add("feast.core-authentication.options.oauth_url", () -> TOKEN_URL);
79+
registry.add("feast.core-authentication.options.grant_type", () -> GRANT_TYPE);
80+
registry.add("feast.core-authentication.options.client_id", () -> CLIENT_ID);
81+
registry.add("feast.core-authentication.options.client_secret", () -> CLIENT_SECRET);
82+
registry.add("feast.core-authentication.options.audience", () -> AUDIENCE);
83+
registry.add("feast.core-authentication.options.jwkEndpointURI", () -> JWK_URI);
84+
registry.add("feast.security.authentication.options.jwkEndpointURI", () -> JWK_URI);
85+
}
86+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright 2018-2020 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.serving.it;
18+
19+
import feast.proto.core.CoreServiceGrpc;
20+
import feast.proto.core.CoreServiceProto;
21+
import feast.proto.core.FeatureSetProto;
22+
23+
public class CoreSimpleAPIClient {
24+
private CoreServiceGrpc.CoreServiceBlockingStub stub;
25+
26+
public CoreSimpleAPIClient(CoreServiceGrpc.CoreServiceBlockingStub stub) {
27+
this.stub = stub;
28+
}
29+
30+
public void simpleApplyFeatureSet(FeatureSetProto.FeatureSet featureSet) {
31+
stub.applyFeatureSet(
32+
CoreServiceProto.ApplyFeatureSetRequest.newBuilder().setFeatureSet(featureSet).build());
33+
}
34+
35+
public FeatureSetProto.FeatureSet simpleGetFeatureSet(String projectName, String name) {
36+
return stub.getFeatureSet(
37+
CoreServiceProto.GetFeatureSetRequest.newBuilder()
38+
.setName(name)
39+
.setProject(projectName)
40+
.build())
41+
.getFeatureSet();
42+
}
43+
}

0 commit comments

Comments
 (0)