Skip to content

Fix TCP client seeing "already connected" error when resend message after having exception in the first send #94

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 18, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ We have 2 different types of tests:
```sh
export AWS_ACCESS_KEY_ID=YOUR_ACCESS_KEY_ID
export AWS_SECRET_ACCESS_KEY=YOUR_ACCESS_KEY
export AWS_SESSION_TOKEN=YOUR_AWS_SESSION_TOKEN
export AWS_REGION=us-west-2
./gradlew integ
```
Expand Down
3 changes: 3 additions & 0 deletions bin/start-agent.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# usage:
# export AWS_ACCESS_KEY_ID=
# export AWS_SECRET_ACCESS_KEY=
# export AWS_SESSION_TOKEN=
# export AWS_REGION=us-west-2
# ./start-agent.sh

Expand All @@ -22,6 +23,7 @@ pushd $rootdir/src/integration-test/resources/agent
echo "[AmazonCloudWatchAgent]
aws_access_key_id = $AWS_ACCESS_KEY_ID
aws_secret_access_key = $AWS_SECRET_ACCESS_KEY
aws_session_token = $AWS_SESSION_TOKEN
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you try running integration tests with an IAM user with no session token variable set, and see if this still works?

Also, we'll want this on master later as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay cool. We still want to be using temporary credentials with a session token when possible, I just want to make sure it will still continue to work with long-lived credentials (if someone running the tests chooses to do that).

" > ./.aws/credentials

echo "[profile AmazonCloudWatchAgent]
Expand All @@ -33,5 +35,6 @@ docker run -p 25888:25888/udp -p 25888:25888/tcp \
-e AWS_REGION=$AWS_REGION \
-e AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \
-e AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \
-e AWS_SESSION_TOKEN=$AWS_SESSION_TOKEN \
agent:latest &> $tempfile &
popd
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ dependencies {
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.11.1'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.1'
implementation 'org.slf4j:slf4j-api:1.7.30'
implementation 'org.slf4j:slf4j-simple:1.7.30'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be needed

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without this, we'd see

software.amazon.cloudwatchlogs.emf.MetricsLoggerIntegrationTest > testMultipleFlushesOverTCP STANDARD_ERROR
    SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
    SLF4J: Defaulting to no-operation (NOP) logger implementation
    SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is okay, we don't want or need to provide a logging implementation. User's provide their own logger.

implementation 'org.javatuples:javatuples:1.2'

// Use JUnit test framework
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@ public class TCPClient implements SocketClient {
private boolean shouldConnect = true;

public TCPClient(Endpoint endpoint) {
socket = createSocket();
this.endpoint = endpoint;
}

private void connect() {
try {
// Avoid "socket already connected" error (https://issues.amazon.com/issues/P54323886)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the link from this comment. Also, the comment might not even be needed. If we keep it, you should be more descriptive as to why we want to create a new socket on every connect() call.

socket = createSocket();
socket.connect(new InetSocketAddress(endpoint.getHost(), endpoint.getPort()));
shouldConnect = false;
} catch (Exception e) {
Expand All @@ -51,7 +52,7 @@ protected Socket createSocket() {

@Override
public synchronized void sendMessage(String message) {
if (socket.isClosed() || shouldConnect) {
if (socket == null || socket.isClosed() || shouldConnect) {
connect();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,50 @@ protected Socket createSocket() {

assertEquals(bos.toString(), message);
}

@Test
public void testSendMessageWithGetOSException_THEN_createSocketTwice() throws IOException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once this PR is merged can we also add these tests to the master branch?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I believe this also apply to that

Socket socket = mock(Socket.class);
doNothing().when(socket).connect(any());
when(socket.getOutputStream()).thenThrow(IOException.class);

Endpoint endpoint = Endpoint.DEFAULT_TCP_ENDPOINT;
TCPClient client =
new TCPClient(endpoint) {
@Override
protected Socket createSocket() {
return socket;
}
};

TCPClient spyClient = spy(client);

String message = "Test message";
spyClient.sendMessage(message);
verify(spyClient, atLeast(2)).createSocket();
}

@Test
public void testSendMessageWithWriteOSException_THEN_createSocketTwice() throws IOException {
Socket socket = mock(Socket.class);
doNothing().when(socket).connect(any());
ByteArrayOutputStream bos = mock(ByteArrayOutputStream.class);
when(socket.getOutputStream()).thenReturn(bos);
doThrow(IOException.class).when(bos).write(any());

Endpoint endpoint = Endpoint.DEFAULT_TCP_ENDPOINT;
TCPClient client =
new TCPClient(endpoint) {
@Override
protected Socket createSocket() {
return socket;
}
};

TCPClient spyClient = spy(client);

String message = "Test message";
spyClient.sendMessage(message);
verify(spyClient, atLeast(2)).createSocket();
}
}