Skip to content
This repository was archived by the owner on Jun 2, 2025. It is now read-only.

Commit fd0fc4b

Browse files
author
My Name
committed
Implement mutiple inputs and update mleap dependency version with 0.14.0
1 parent fda745a commit fd0fc4b

File tree

8 files changed

+153
-25
lines changed

8 files changed

+153
-25
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ WORKDIR /sagemaker-sparkml-model-server
99

1010
RUN mvn clean package
1111

12-
RUN cp ./target/sparkml-serving-2.3.jar /usr/local/lib/sparkml-serving-2.3.jar
12+
RUN cp ./target/sparkml-serving-2.4.jar /usr/local/lib/sparkml-serving-2.4.jar
1313
RUN cp ./serve.sh /usr/local/bin/serve.sh
1414

1515
RUN chmod a+x /usr/local/bin/serve.sh

README.md

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -223,20 +223,20 @@ Calling `CreateModel` is required for creating a `Model` in SageMaker with this
223223
SageMaker works with Docker images stored in [Amazon ECR](https://aws.amazon.com/ecr/). SageMaker team has prepared and uploaded the Docker images for SageMaker SparkML Serving Container in all regions where SageMaker operates.
224224
Region to ECR container URL mapping can be found below. For a mapping from Region to Region Name, please see [here](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html).
225225

226-
* us-west-1 = 746614075791.dkr.ecr.us-west-1.amazonaws.com/sagemaker-sparkml-serving:2.2
227-
* us-west-2 = 246618743249.dkr.ecr.us-west-2.amazonaws.com/sagemaker-sparkml-serving:2.2
228-
* us-east-1 = 683313688378.dkr.ecr.us-east-1.amazonaws.com/sagemaker-sparkml-serving:2.2
229-
* us-east-2 = 257758044811.dkr.ecr.us-east-2.amazonaws.com/sagemaker-sparkml-serving:2.2
230-
* ap-northeast-1 = 354813040037.dkr.ecr.ap-northeast-1.amazonaws.com/sagemaker-sparkml-serving:2.2
231-
* ap-northeast-2 = 366743142698.dkr.ecr.ap-northeast-2.amazonaws.com/sagemaker-sparkml-serving:2.2
232-
* ap-southeast-1 = 121021644041.dkr.ecr.ap-southeast-1.amazonaws.com/sagemaker-sparkml-serving:2.2
233-
* ap-southeast-2 = 783357654285.dkr.ecr.ap-southeast-2.amazonaws.com/sagemaker-sparkml-serving:2.2
234-
* ap-south-1 = 720646828776.dkr.ecr.ap-south-1.amazonaws.com/sagemaker-sparkml-serving:2.2
235-
* eu-west-1 = 141502667606.dkr.ecr.eu-west-1.amazonaws.com/sagemaker-sparkml-serving:2.2
236-
* eu-west-2 = 764974769150.dkr.ecr.eu-west-2.amazonaws.com/sagemaker-sparkml-serving:2.2
237-
* eu-central-1 = 492215442770.dkr.ecr.eu-central-1.amazonaws.com/sagemaker-sparkml-serving:2.2
238-
* ca-central-1 = 341280168497.dkr.ecr.ca-central-1.amazonaws.com/sagemaker-sparkml-serving:2.2
239-
* us-gov-west-1 = 414596584902.dkr.ecr.us-gov-west-1.amazonaws.com/sagemaker-sparkml-serving:2.2
226+
* us-west-1 = 746614075791.dkr.ecr.us-west-1.amazonaws.com/sagemaker-sparkml-serving:2.4
227+
* us-west-2 = 246618743249.dkr.ecr.us-west-2.amazonaws.com/sagemaker-sparkml-serving:2.4
228+
* us-east-1 = 683313688378.dkr.ecr.us-east-1.amazonaws.com/sagemaker-sparkml-serving:2.4
229+
* us-east-2 = 257758044811.dkr.ecr.us-east-2.amazonaws.com/sagemaker-sparkml-serving:2.4
230+
* ap-northeast-1 = 354813040037.dkr.ecr.ap-northeast-1.amazonaws.com/sagemaker-sparkml-serving:2.4
231+
* ap-northeast-2 = 366743142698.dkr.ecr.ap-northeast-2.amazonaws.com/sagemaker-sparkml-serving:2.4
232+
* ap-southeast-1 = 121021644041.dkr.ecr.ap-southeast-1.amazonaws.com/sagemaker-sparkml-serving:2.4
233+
* ap-southeast-2 = 783357654285.dkr.ecr.ap-southeast-2.amazonaws.com/sagemaker-sparkml-serving:2.4
234+
* ap-south-1 = 720646828776.dkr.ecr.ap-south-1.amazonaws.com/sagemaker-sparkml-serving:2.4
235+
* eu-west-1 = 141502667606.dkr.ecr.eu-west-1.amazonaws.com/sagemaker-sparkml-serving:2.4
236+
* eu-west-2 = 764974769150.dkr.ecr.eu-west-2.amazonaws.com/sagemaker-sparkml-serving:2.4
237+
* eu-central-1 = 492215442770.dkr.ecr.eu-central-1.amazonaws.com/sagemaker-sparkml-serving:2.4
238+
* ca-central-1 = 341280168497.dkr.ecr.ca-central-1.amazonaws.com/sagemaker-sparkml-serving:2.4
239+
* us-gov-west-1 = 414596584902.dkr.ecr.us-gov-west-1.amazonaws.com/sagemaker-sparkml-serving:2.4
240240

241241
With [SageMaker Python SDK](https://github.com/aws/sagemaker-python-sdk)
242242
------------------------------------------------------------------------
@@ -263,7 +263,7 @@ First you need to ensure that have installed [Docker](https://www.docker.com/) o
263263
In order to build the Docker image, you need to run a single Docker command:
264264

265265
```
266-
docker build -t sagemaker-sparkml-serving:2.2 .
266+
docker build -t sagemaker-sparkml-serving:2.4 .
267267
```
268268

269269
#### Running the image locally
@@ -272,7 +272,7 @@ In order to run the Docker image, you need to run the following command. Please
272272
The command will start the server on port 8080 and will also pass the schema as an environment variable to the Docker container. Alternatively, you can edit the `Dockerfile` to add `ENV SAGEMAKER_SPARKML_SCHEMA=schema` as well before building the Docker image.
273273

274274
```
275-
docker run -p 8080:8080 -e SAGEMAKER_SPARKML_SCHEMA=schema -v /tmp/model:/opt/ml/model sagemaker-sparkml-serving:2.2 serve
275+
docker run -p 8080:8080 -e SAGEMAKER_SPARKML_SCHEMA=schema -v /tmp/model:/opt/ml/model sagemaker-sparkml-serving:2.4 serve
276276
```
277277

278278
#### Invoking with a payload
@@ -287,7 +287,7 @@ or
287287
curl -i -H "content-type:application/json" -d "{\"data\":[feature_1,\"feature_2\",feature_3]}" http://localhost:8080/invocations
288288
```
289289

290-
The `Dockerfile` can be found at the root directory of the package. SageMaker SparkML Serving Container tags the Docker images using the Spark major version it is compatible with. Right now, it only supports Spark 2.2 and as a result, the Docker image is tagged with 2.2.
290+
The `Dockerfile` can be found at the root directory of the package. SageMaker SparkML Serving Container tags the Docker images using the Spark major version it is compatible with. Right now, it only supports Spark 2.4 and as a result, the Docker image is tagged with 2.4.
291291

292292
In order to save the effort of building the Docker image everytime you are making a code change, you can also install [Maven](http://maven.apache.org/) and run `mvn clean package` at your project root to verify if the code is compiling fine and unit tests are running without any issue.
293293

@@ -310,7 +310,7 @@ aws ecr get-login --region us-west-2 --registry-ids 246618743249 --no-include-em
310310
* Download the Docker image with the following command:
311311

312312
```
313-
docker pull 246618743249.dkr.ecr.us-west-2.amazonaws.com/sagemaker-sparkml-serving:2.2
313+
docker pull 246618743249.dkr.ecr.us-west-2.amazonaws.com/sagemaker-sparkml-serving:2.4
314314
```
315315

316316
For running the Docker image, please see the Running the image locally section from above.

ci/buildspec.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ phases:
99
commands:
1010
- echo Build started on `date`
1111
- echo Building the Docker image...
12-
- docker build -t sagemaker-sparkml-serving:2.3 .
13-
- docker tag sagemaker-sparkml-serving:2.3 515193369038.dkr.ecr.us-west-2.amazonaws.com/sagemaker-sparkml-serving:2.3
12+
- docker build -t sagemaker-sparkml-serving:2.4 .
13+
- docker tag sagemaker-sparkml-serving:2.4 515193369038.dkr.ecr.us-west-2.amazonaws.com/sagemaker-sparkml-serving:2.4
1414
post_build:
1515
commands:
1616
- echo Build completed on `date`
1717
- echo Pushing the Docker image...
18-
- docker push 515193369038.dkr.ecr.us-west-2.amazonaws.com/sagemaker-sparkml-serving:2.3
18+
- docker push 515193369038.dkr.ecr.us-west-2.amazonaws.com/sagemaker-sparkml-serving:2.4

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
<modelVersion>4.0.0</modelVersion>
2525
<groupId>org.amazonaws.sagemaker</groupId>
2626
<artifactId>sparkml-serving</artifactId>
27-
<version>2.3</version>
27+
<version>2.4</version>
2828
<build>
2929
<plugins>
3030
<plugin>
@@ -154,7 +154,7 @@
154154
<dependency>
155155
<groupId>ml.combust.mleap</groupId>
156156
<artifactId>mleap-runtime_2.11</artifactId>
157-
<version>0.13.0</version>
157+
<version>0.14.0</version>
158158
</dependency>
159159
<dependency>
160160
<groupId>org.apache.commons</groupId>

serve.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
#!/bin/bash
22
# This is needed to make sure Java correctly detects CPU/Memory set by the container limits
3-
java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -jar /usr/local/lib/sparkml-serving-2.3.jar
3+
java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -jar /usr/local/lib/sparkml-serving-2.4.jar

src/main/java/com/amazonaws/sagemaker/controller/ServingController.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import com.amazonaws.sagemaker.dto.BatchExecutionParameter;
2323
import com.amazonaws.sagemaker.dto.DataSchema;
24+
import com.amazonaws.sagemaker.dto.SageMakerDataListObject;
2425
import com.amazonaws.sagemaker.dto.SageMakerRequestObject;
2526
import com.amazonaws.sagemaker.helper.DataConversionHelper;
2627
import com.amazonaws.sagemaker.helper.ResponseHelper;
@@ -43,6 +44,7 @@
4344
import org.slf4j.LoggerFactory;
4445
import org.springframework.beans.factory.annotation.Autowired;
4546
import org.springframework.http.HttpHeaders;
47+
import org.springframework.http.HttpStatus;
4648
import org.springframework.http.MediaType;
4749
import org.springframework.http.ResponseEntity;
4850
import org.springframework.web.bind.annotation.RequestBody;
@@ -148,6 +150,54 @@ public ResponseEntity<String> transformRequestCsv(@RequestBody final byte[] csvR
148150
}
149151
}
150152

153+
/**
154+
* Implements the invocations POST API for application/jsonlines input
155+
*
156+
* @param jsonLines, lines of json values
157+
* @param accept, accept parameter from request
158+
* @return ResponseEntity with body as the expected payload JSON & proper statuscode based on the input
159+
*/
160+
@RequestMapping(path = "/invocations", method = POST, consumes = AdditionalMediaType.APPLICATION_JSONLINES_VALUE)
161+
public ResponseEntity<String> transformRequestJsonLines(@RequestBody final byte[] jsonLines,
162+
@RequestHeader(value = HttpHeaders.ACCEPT, required = false) final String accept) {
163+
if (jsonLines == null) {
164+
LOG.error("Input passed to the request is empty");
165+
return ResponseEntity.noContent().build();
166+
}
167+
try {
168+
final String acceptVal = this.retrieveAndVerifyAccept(accept);
169+
final DataSchema schema = this.retrieveAndVerifySchema(null, mapper);
170+
final String jsonStringLine = new String(jsonLines);
171+
172+
// Map list of inputs to DataList object
173+
final SageMakerDataListObject sro = mapper.readValue(jsonStringLine, SageMakerDataListObject.class);
174+
List<List<Object>> inputDatas = sro.getData();
175+
List<ResponseEntity<String>> responseList = Lists.newArrayList();
176+
177+
// Process each input separately and add response to a list
178+
final int inputDatasSize = inputDatas.size();
179+
for (int idx = 0; idx < inputDatasSize; ++idx) {
180+
ResponseEntity<String> response = this.processInputData(inputDatas.get(idx), schema, acceptVal);
181+
responseList.add(response);
182+
}
183+
184+
// Merge response body to a new output response
185+
List<List<String>> bodyList = Lists.newArrayList();
186+
HttpHeaders headers = null;
187+
//combine body in responseList
188+
for (ResponseEntity<String> response:responseList) {
189+
HttpStatus statuscode = response.getStatusCode();
190+
headers = response.getHeaders();
191+
bodyList.add(Lists.newArrayList(response.getBody()));
192+
}
193+
194+
return ResponseEntity.ok().headers(headers).body(bodyList.toString());
195+
} catch (final Exception ex) {
196+
LOG.error("Error in processing current request", ex);
197+
return ResponseEntity.badRequest().body(ex.getMessage());
198+
}
199+
}
200+
151201
@VisibleForTesting
152202
protected String retrieveAndVerifyAccept(final String acceptFromRequest) {
153203
final String acceptVal = checkEmptyAccept(acceptFromRequest) ? SystemUtils
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2010-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*
15+
*/
16+
17+
package com.amazonaws.sagemaker.dto;
18+
19+
import com.fasterxml.jackson.annotation.JsonCreator;
20+
import com.fasterxml.jackson.annotation.JsonProperty;
21+
import com.google.common.base.Preconditions;
22+
23+
import java.util.List;
24+
25+
/**
26+
* Request object POJO to which input request in JSONLINES format will be mapped to by Spring (using Jackson). For sample
27+
* input, please see test/resources/com/amazonaws/sagemaker/dto
28+
*/
29+
public class SageMakerDataListObject {
30+
31+
private List<List<Object>> data;
32+
33+
@JsonCreator
34+
public SageMakerDataListObject(
35+
@JsonProperty("data") final List<List<Object>> data) {
36+
this.data = Preconditions.checkNotNull(data);
37+
}
38+
39+
public List<List<Object>> getData() {
40+
return data;
41+
}
42+
}

src/test/java/com/amazonaws/sagemaker/controller/ServingControllerTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,42 @@ public void testListValueMLeapThrowsExceptionCsvApi() {
230230
Assert.assertEquals(output.getBody(), "input data is not valid");
231231
}
232232

233+
@Test
234+
public void testJsonLinesApiWithListInput() {
235+
schemaInJson = "{\"input\":[{\"name\":\"test_name_1\",\"type\":\"int\"},{\"name\":\"test_name_2\","
236+
+ "\"type\":\"double\"},{\"name\":\"test_name_3\",\"type\":\"string\"}],\"output\":{\"name\":\"out_name\",\"type\":\"int\",\"struct\":\"vector\"}}";
237+
List<Object> outputResponseForFirstInput = Lists.newArrayList(1, 2);
238+
List<Object> outputResponseForSecondInput = Lists.newArrayList(3, 4);
239+
final String expectOutput = "[[1,2], [3,4]]";
240+
241+
PowerMockito.when(SystemUtils.getEnvironmentVariable("SAGEMAKER_SPARKML_SCHEMA")).thenReturn(schemaInJson);
242+
PowerMockito
243+
.when(ScalaUtils.getJavaObjectIteratorFromArrayRow(Mockito.any(ArrayRow.class), Mockito.anyString()))
244+
.thenReturn(outputResponseForFirstInput.iterator())
245+
.thenReturn(outputResponseForSecondInput.iterator());
246+
final ResponseEntity<String> output = controller.transformRequestJsonLines("{\"data\":[[1,2.0,\"TEST1\"], [2,3.0,\"TEST\"]]}".getBytes(), "text/csv");
247+
Assert.assertEquals(expectOutput, output.getBody());
248+
}
249+
250+
@Test
251+
public void testJsonLinesApiWithListInputThorwsException() {
252+
schemaInJson = "{\"input\":[{\"name\":\"test_name_1\",\"type\":\"int\"},{\"name\":\"test_name_2\","
253+
+ "\"type\":\"double\"},{\"name\":\"test_name_3\",\"type\":\"string\"}],\"output\":{\"name\":\"out_name\",\"type\":\"int\",\"struct\":\"vector\"}}";
254+
PowerMockito.when(SystemUtils.getEnvironmentVariable("SAGEMAKER_SPARKML_SCHEMA")).thenReturn(schemaInJson);
255+
PowerMockito
256+
.when(ScalaUtils.getJavaObjectIteratorFromArrayRow(Mockito.any(ArrayRow.class), Mockito.anyString()))
257+
.thenThrow(new RuntimeException("input data is not valid"));
258+
final ResponseEntity<String> output = controller.transformRequestJsonLines("{\"data\":[[1,2.0,\"TEST1\"], [2,3.0,\"TEST\"]]}".getBytes(), "text/csv");
259+
Assert.assertEquals(output.getStatusCode(), HttpStatus.BAD_REQUEST);
260+
Assert.assertEquals(output.getBody(), "input data is not valid");
261+
}
262+
263+
@Test
264+
public void testJsonLinesApiWithNullInput() {
265+
PowerMockito.when(SystemUtils.getEnvironmentVariable("SAGEMAKER_SPARKML_SCHEMA")).thenReturn(schemaInJson);
266+
final ResponseEntity<String> output = controller.transformRequestJsonLines(null, "text/csv");
267+
Assert.assertEquals(output.getStatusCode(), HttpStatus.NO_CONTENT);
268+
}
233269

234270
@Test
235271
public void testParseAcceptEmptyFromRequestEnvironmentPresent() {

0 commit comments

Comments
 (0)