Sad that Scala IDE for Eclipse is no longer supported. While it was a great to have Scala integrated within Eclipse, guess the headwinds were too strong!
Insights on Java, Big Data, Search, Cloud, Algorithms, Data Science, Machine Learning...
Saturday, November 30, 2024
Scala IDE no more
Thursday, November 28, 2024
Working with Moto & Lambci Lambda Docker Images
Next up on Mock for clouds is Moto. Moto is primarily for running tests within the Python ecosystem.
Moto does offer a standalone server mode for a other langauges. General sense was that the standalone Moto server would offer the AWS services which will be accessible from the cli & non-Python SDKs. Gave Moto a shot with the same AWS services tried with Localstack.
(I) Set-up
While installing Moto ran into a couple of dependency conflicts across moto, boto3, botocore, requests, s3transfer & in turn with the installed awscli. With some effort reached a sort of dynamic equillibrium with (installed via pip):
- awscli 1.36.11
- boto3 1.35.63
- botocore 1.35.70
- moto 5.0.21
- requests 2.32.2
- s3transfer 0.10.4
(II) Start Moto Server
# Start Moto
moto_server -p3000
# Start Moto as Docker (Sticking to this option)
docker run --rm -p 5000:5000 --name moto motoserver/moto:latest
(III) Invoke services on Moto
(a) S3
# Create bucket
aws --endpoint-url=http://localhost:5000 s3 mb s3://test-buck
# Copy item to bucket
aws --endpoint-url=http://localhost:5000 s3 cp a1.txt s3://test-buck
# List bucket
aws --endpoint-url=http://localhost:5000 s3 ls s3://test-buck
--
(b) SQS
# Create queue
aws --endpoint-url=http://localhost:5000 sqs create-queue --queue-name test-q
# List queues
aws --endpoint-url=http://localhost:5000 sqs list-queues
# Get queue attribute
aws --endpoint-url=http://localhost:5000 sqs get-queue-attributes --queue-url http://localhost:5000/123456789012/test-q --attribute-names All
--
(c) IAM
## Issue: Moto does a basic check of user role & gives an AccessDeniedException when calling Lambda CreateFunction operation
## So have to create a specific IAM role (https://github.com/getmoto/moto/issues/3944#issuecomment-845144036) in Moto for the purpose.
aws iam --region=us-east-1 --endpoint-url=http://localhost:5000 create-role --role-name "lambda-test-role" --assume-role-policy-document "some policy" --path "/lambda-test/"
--
(d) Lambda
# Create Java function
aws --endpoint-url=http://localhost:5000 lambda create-function --function-name test-j-div --zip-file fileb://original-java-basic-1.0-SNAPSHOT.jar --handler example.HandlerDivide::handleRequest --runtime java8.al2 --role arn:aws:iam::123456789012:role/lambda-test/lambda-test-role
# List functions
aws --endpoint-url=http://localhost:5000 lambda list-functions
# Invoke function (Fails!)
aws --endpoint-url=http://localhost:5000 lambda invoke --function-name test-j-div --payload '[235241,17]' outputJ.txt
The invoke function fails with the message:
"WARNING - Unable to parse Docker API response. Defaulting to 'host.docker.internal'
<class 'json.decoder.JSONDecodeError'>::Expecting value: line 1 column 1 (char 0)
error running docker: Error while fetching server API version: ('Connection aborted.', FileNotFoundError(2, 'No such file or directory'))".
Retried this from AWS Java-SDK & for other nodejs & python function but nothing worked. While this remains unsolved for now, check out Lambci docker option next.
(IV) Invoke services on Lambci Lambda Docker Images:
Moto Lambda docs also mention its dependent docker images from the lambci/lambda & mlupin/docker-lambda (for new ones). Started off with a slightly older java8.al2 docker image from lambci/lambda.
# Download lambci/lambda:java8.al2
docker pull lambci/lambda:java8.al2
# Run lambci/lambda:java8.al2.
## Ensure to run from the location which has the unzipped (unjarred) Java code
## Here it's run from a folder called data_dir_java which has the unzipped (unjarred) class file folders: com/, example/, META-INF/, net/
docker run -e DOCKER_LAMBDA_STAY_OPEN=1 -p 9001:9001 -v "$PWD":/var/task:ro,delegated --name lambcijava8al2 lambci/lambda:java8.al2 example.HandlerDivide::handleRequest
# Invoke Lambda
aws --endpoint-url=http://localhost:9001 lambda invoke --function-name test-j-div --payload '[235241,17]' outputJ.txt
This works!
Tuesday, November 26, 2024
AWS Lambda on Localstack using Java-SdK-v1
Continuing with Localstack, next is a closer look into the code to deploy and execute AWS Lambda code on Localstack from AWS Java-Sdk-v1. The localstack-lambda-java-sdk-v1 code uses the same structure used in localstack-aws-sdk-examples & fills in for the missing AWS Lambda bit.
The LambdaService class has 3 primary methods - listFunctions(), createFunction() & invokeFunction(). The static AWSLambda client is setup with Mock credentials and pointing to the Localstack endpoint.
The main() method first creates the function (createFunction()), if it does not exist.
- It builds a CreateFunctionRequest object with the handler, runtime, role, etc specified
- It also reads the jar file of the Java executable from the resources folder into a FunctionCode object & adds it to the CreateFunctionRequest
- Next a call is made to the AWSLambda client createFunction() with the CreateFunctionRequest which hits the running Localstack instance (Localstack set-up explained earlier).
If all goes well, control returns to main() which invokes the listFunctions() to show details of the created Lambda function (& all others functions existing).
Finally, there is call from main() to invokeFunction() method.
- Which invokes the recently created function with a InvokeRequest object filled with some test values as the payload.
- The response from the invoked function is a InvokeResult object who's payload contains the results of the lambda function computation.
Comments welcome, localstack-lambda-java-sdk-v1 is available to play around!
Monday, November 25, 2024
Getting Localstack Up and Running
In continuation to the earlier post on mocks for clouds, this article does a deep dive into getting up & running with Localstack. This is a consolidation of the steps & best practices shared here, here & here. The Localstack set-up is on a Ubuntu-20.04, with Java-8x, Maven-3.8x, Docker-24.0x.
(I) Set-up
# Install awscli
sudo apt-get install awscli
# Install localstack ver 3.8
## Issue1: By default pip pulls in version 4.0, which gives an error:
## ERROR: Could not find a version that satisfies the requirement localstack-ext==4.0.0 (from localstack)
python3 -m pip install localstack==3.8.1
--
# Add to /etc/hosts
127.0.0.1 localhost.localstack.cloud
127.0.0.1 s3.localhost.localstack.cloud
--
# Configure AWS from cli
aws configure
aws configure set default.region us-east-1
aws configure set aws_access_key_id test
aws configure set aws_secret_access_key test
## Manually configure AWS
Add to ~/.aws/config
endpoint_url = http://localhost:4566
## Add mock credentials
Add to ~/.aws/credentials
aws_access_key_id = test
aws_secret_access_key = test
--
# Download docker images needed by the Lambda function
## Issue 2: Do this before hand, Localstack gets stuck
## at the download image stage unless it's already available
## Pull java:8.al2
docker pull public.ecr.aws/lambda/java:8.al2
## Pull nodejs (required for other nodejs Lambda functions)
docker pull public.ecr.aws/lambda/nodejs:18
## Check images downloaded
docker image ls
(II) Start Localstack
# Start locally
localstack start
# Start as docker (add '-d' for daemon)
## Issue 3: Local directory's mount should be as per sample docker-compose
docker-compose -f docker-compose-localstack.yaml up
# Localstack up on URL's
http://localhost:4566
http://localhost.localstack.cloud:4566
# Check Localstack Health
curl http://localhost:4566/_localstack/info
curl http://localhost:4566/_localstack/health
(III) AWS services on Localstack from CLI
(a) S3
# Create bucket named "test-buck"
aws --endpoint-url=http://localhost:4566 s3 mb s3://test-buck
# Copy item to bucket
aws --endpoint-url=http://localhost:4566 s3 cp a1.txt s3://test-buck
# List bucket
aws --endpoint-url=http://localhost:4566 s3 ls s3://test-buck
--
(b) Sqs
# Create queue named "test-q"
aws --endpoint-url=http://localhost:4566 sqs create-queue --queue-name test-q
# List queues
aws --endpoint-url=http://localhost:4566 sqs list-queues
# Get queue attribute
aws --endpoint-url=http://localhost:4566 sqs get-queue-attributes --queue-url http://sqs.us-east-1.localhost.localstack.cloud:4566/000000000000/test-q --attribute-names All
--
(c) Lambda
aws --endpoint-url=http://localhost:4566 lambda list-functions
# Create Java function
aws --endpoint-url=http://localhost:4566 lambda create-function --function-name test-j-div --zip-file fileb://original-java-basic-1.0-SNAPSHOT.jar --handler example.HandlerDivide::handleRequest --runtime java8.al2 --role arn:aws:iam::000000000000:role/lambda-test
# List functions
aws --endpoint-url=http://localhost:4566 lambda list-functions
# Invoke Java function
aws --endpoint-url=http://localhost:4566 lambda invoke --function-name test-j-div --payload '[200,9]' outputJ.txt
# Delete function
aws --endpoint-url=http://localhost:4566 lambda delete-function --function-name test-j-div
(IV) AWS services on Localstack from Java-SDK
# For S3 & Sqs - localstack-aws-sdk-examples, java sdk
# For Lambda - localstack-lambda-java-sdk-v1
Thursday, November 21, 2024
Killing me softly
With your air. With your smog. With your AQIs. With your chart topping PM levels. Delhi this annual event of yours, wish we could skip!
Familiar noises echoing from the four estates are no balm to the troubled sinuses. They shout at the top of their lungs, we cough & sneeze from the bottom of ours.
Solution, now what's that? From whom, when, where & why? Since one's can't really run away perhaps we need to just hibernate or hide. Better still, grin and bear this way of lieF (sic).
Saturday, November 16, 2024
Mutable Argument Capture with Mockito
There are well known scenarios like caching, pooling, etc wherein object reuse is common. Testing these cases using a framework like Mockito could run into problems. Esp if there's a need to verify the arguments sent by the Caller of a Service, where the Service is mocked.
ArgumentCaptor (mockito) fails because it keeps references to the argument obj, which due to reuse by the caller only have the last/ latest updated value.
The discussion here led to using Void Answer as one possible way to solve the issue. The following (junit-3+, mockito-1.8+, commons-lang-2.5) code explains the details.
1. Service:
public class Service {
public void serve(MutableInt value) {
System.out.println("Service.serve(): "+value);
}
2. Caller:
public class Caller {
public void callService(Service service) {
MutableInt value = new MutableInt();
value.setValue(1);
service.serve(value);
value.setValue(2);
service.serve(value);
}
...
3.Tests:
public class MutableArgsTest extends TestCase{
List<MutableInt> multiValuesWritten;
@Mock
Service service;
/**
* Failure with ArgumentCaptor
*/
public void testMutableArgsWithArgCaptorFail() {
Caller caller = new Caller();
ArgumentCaptor<MutableInt> valueCaptor =
ArgumentCaptor.forClass(MutableInt.class);
caller.callService(service);
verify(service,times(2)).serve(valueCaptor.capture());
// AssertionFailedError: expected:<[1, 2]> but was:<[2, 2]>"
assertEquals(Arrays.asList(new MutableInt(1),
new MutableInt(2)),valueCaptor.getAllValues());
}
/**
* Success with Answer
*/
public void testMutableArgsWithDoAnswer() {
Caller caller = new Caller();
doAnswer(new CaptureArgumentsWrittenAsMutableInt<Void>()).
when(service).serve(any(MutableInt.class));
caller.callService(service);
verify(service,times(2)).serve(any(MutableInt.class));
// Works!
assertEquals(new MutableInt(1),multiValuesWritten.get(0));
assertEquals(new MutableInt(2),multiValuesWritten.get(1));
}
/**
* Captures Arguments to the Service.serve() method:
* - Multiple calls to serve() happen from the same caller
* - Along with reuse of MutableInt argument objects by the caller
* - Argument value is copied to a new MutableInt object & that's captured
* @param <Void>
*/
public class CaptureArgumentsWrittenAsMutableInt<Void> implements Answer<Void>{
public Void answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
multiValuesWritten.add(new MutableInt(args[0].toString()));
return null ;
}
}
}