jclouds: getBucketLocation timeout on getBlob - amazon-s3

I'm using jclouds 2.5.0. It's working perfectly in all of our deployments except for one. In this case, we're seeing the following jclouds message in our log4j2 logs:
2022-07-14 21:37:29.263 +0000,3124098302712886 {} ERROR o.j.h.h.BackoffLimitedRetryHandler [clrd-highpri-1] Cannot retry after server error, command has exceeded retry limit 5: [method=org.jclouds.aws.s3.AWSS3Client.public abstract java.lang.String org.jclouds.s3.S3Client.getBucketLocation(java.lang.String)[hammerspace-data-bucket-us-west-2], request=GET https://s3.amazonaws.com/hammerspace-data-bucket-us-west-2?location HTTP/1.1]
This message occurs during a getBlob call, so I'm assuming part of getBlob is to determine the bucket from which the blob should be retrieved. This call is failing 5 times - but not just failing with a bad return code - it's hanging and timing out, so these 5 retries are taking up the lion's share of the time it takes to download the blob.
After getBlob finally stops calling getBucketLocation, it then tries the download with the default region (us-east-1). Since the bucket is actually in us-west-2, the download takes a bit longer than it should, but - again - the actual download bottleneck is the failed calls to getBucketLocation.
Has anyone seen anything like this before?
I'd also be interested in knowing how to turn on more jclouds logging. I used to uncomment lines like this in my log4j2.xml file:
<!-- <logger name="org.jclouds" level="debug" additivity="true" /> -->
<!-- <logger name="jclouds.compute" level="debug" additivity="true" /> -->
<!-- <logger name="jclouds.wire" level="debug" additivity="true" /> -->
<!-- <logger name="jclouds.headers" level="debug" additivity="true" /> -->
<!-- <logger name="jclouds.ssh" level="debug" additivity="true" /> -->
<!-- <logger name="software.amazon.awssdk" level="debug" additivity="true" /> -->
<!-- <logger name="org.apache.http.wire" level="debug" additivity="true" /> -->
But these don't seem to have any effect in 2.5.0 anymore.
Finally, if anyone knows how I can stop getBlob from calling getBucketLocation, I'd much appreciate some advice here. I'm thinking there must be a way to specify the desired bucket to the jclouds blob context up front so it doesn't have to resolve it.
John
[Update 1]
We thought originally the problem was we didn't have our AIM profile configured correctly for the bucket, but after playing with it, we were able to run the AWS command line tool from the same host on that bucket and it didn't hang, but jclouds is still hanging on getBucketLocation on the same box. I'm completely stumped by this. It HAS to be something internal to jclouds 2.5.0 with the AWS provider.

I've discovered the root cause of this issue and thought there might be others out there that would like to know what's going on.
Amazon has a general work flow they publish that allows clients to always find the correct URL endpoint for a given bucket:
ask s3.amazonaws.com for the bucket location
use the url returned to make the container specific request (get/put, etc)
If a client is slightly more intelligent, it will ask only on the first request and cache the bucket location URL and reuse it in subsequent requests.
If a client is even more intelligent, and it notices a region-specific URL is specified, it will use the URL directly to attempt a request. Upon failure, it will then call back to the US west coast to get the bucket location, cache it and use it.
Apparently, jclouds is only at intelligence level 1 above. It completely ignores the specified URL, but it does at least cache the results from the first getBucketLocation call and use that region-specific URL, as needed.
Internally, it's using a google guava LoadingCache for this process. It might be nice if there was a mechanism in jclouds to pre-load this cache with known region-specific URLs for a given bucket. Then it would not have to go off box for the getLocation data - even on the first request.
I hope this is helpful to others. It sure cost me a lot of pain to find out. And since I received no answers from any of my jclouds mailing list queries, I have to assume that there was no one in the jclouds community that understood how this worked either. (Or perhaps I just didn't word my query well enough.)
[UPDATE]
I did find a work around for this. I wrote this static inner class in my jclouds-consuming client:
#ConfiguresHttpApi
private static class BucketToRegionHack extends AWSS3HttpApiModule {
private String region;
private String bucket;
public void setBucketForRegion(String region, String bucket) {
this.region = region;
this.bucket = bucket;
}
#Override
#SuppressWarnings("Guava")
protected CacheLoader<String, Optional<String>> bucketToRegion(Supplier<Set<String>> regionSupplier, S3Client client) {
Set<String> regions = regionSupplier.get();
if (regions.isEmpty()) {
return new CacheLoader<String, Optional<String>>() {
#Override
#SuppressWarnings({"Guava", "NullableProblems"})
public Optional<String> load(String bucket) {
if (BucketToRegionHack.this.bucket != null && BucketToRegionHack.this.bucket.equals(bucket)) {
return Optional.of(BucketToRegionHack.this.region);
}
return Optional.absent();
}
#Override
public String toString() {
return "noRegions()";
}
};
} else if (regions.size() == 1) {
final String onlyRegion = Iterables.getOnlyElement(regions);
return new CacheLoader<String, Optional<String>>() {
#SuppressWarnings("OptionalUsedAsFieldOrParameterType")
final Optional<String> onlyRegionOption = Optional.of(onlyRegion);
#Override
#SuppressWarnings("NullableProblems")
public Optional<String> load(String bucket) {
if (BucketToRegionHack.this.bucket != null && BucketToRegionHack.this.bucket.equals(bucket)) {
return Optional.of(BucketToRegionHack.this.region);
}
return onlyRegionOption;
}
#Override
public String toString() {
return "onlyRegion(" + onlyRegion + ")";
}
};
} else {
return new CacheLoader<String, Optional<String>>() {
#Override
#SuppressWarnings("NullableProblems")
public Optional<String> load(String bucket) {
if (BucketToRegionHack.this.bucket != null && BucketToRegionHack.this.bucket.equals(bucket)) {
return Optional.of(BucketToRegionHack.this.region);
}
try {
return Optional.fromNullable(client.getBucketLocation(bucket));
} catch (ContainerNotFoundException e) {
return Optional.absent();
}
}
#Override
public String toString() {
return "bucketToRegion()";
}
};
}
}
}
This is mostly a copy of the code as it exists in S3HttpApiModule in jclouds. Then I added the following snippet to my init code where I'm setting up the JClouds client:
BucketToRegionHack b2mModule = new BucketToRegionHack();
contextBuilder.modules(ImmutableSet.of(b2mModule));
Pattern pattern = Pattern.compile("s3-([a-z0-9-]+)\\.amazonaws.com");
Matcher matcher = pattern.matcher(endpoint);
if (matcher.find()) {
String region = matcher.group(1);
b2mModule.setBucketForRegion(region, cspInfo.getContainer());
}
...where 'contextBuilder' is the jclouds context builder I'm using. This essentially overrides the S3HttpApiModule with my own version, which allows me to provide my own bucket-to-region method that pre-loads the LoadingCache with my known bucket and region.
A better fix for this would expose a way for users to simply preload the loading cache with a map of buckets to regions so no calls to getBucketLocation would be made for those that are pre-loaded.

Related

Tapestry: How to redirect deprecated URLs to an error page

I have legacy web application built using apache Tapestry. I have deprecated most of the application's functionality except few pages. I want this application to be running in production, but I want to redirect deprecated pages/URLs to some error page with 404 error code. Where should I configure it? I have web.xml and jboss-web.xml. Do I need to do it in some Tapestry configuration file?
You can contribute a RequestFilter to the RequestHandler service, i.e. in your AppModule:
public void contributeRequestHandler(
OrderedConfiguration<RequestFilter> configuration)
{
// Each contribution to an ordered configuration has a name,
// When necessary, you may set constraints to precisely control
// the invocation order of the contributed filter within the pipeline.
configuration.add("DeprecatedURLs", new RequestFilter() {
#Override
public boolean service(Request request,
Response response,
RequestHandler handler) throws IOException
{
String path = request.getPath();
if (isDeprecated(path))
{
response.sendError(404, "Not found");
return;
}
return handler.service(request, response);
}
}, "before:*");
}
Notice the before:* ordering constraint, it should register this filter as the first in RequestHandler's configuration.

Camel aws-s3 not working

I am trying to create a camel route to transfer a file from an FTP server to an AWS S3 storage.
I have written the following route
private static class MyRouteBuilder extends RouteBuilder {
#Override
public void configure() throws Exception
{
from("sftp://<<ftp_server_name>>&noop=true&include=<<file_name>>...")
.process(new Processor(){
#Override
public void process(Exchange ex)
{
System.out.println("Hello");
}
})
.to("aws-s3://my-dev-bucket ?
accessKey=ABC***********&secretKey=12abc********+**********");
}
The issue is, this gives me the following exception:
Exception in thread "main" org.apache.camel.FailedToCreateRouteException: Failed to create route route1 at: >>> To[aws-s3://my-dev-bucket?accessKey=ABC*******************&secretKey=123abc******************** <<< in route: Route(route1)[[From[sftp://<<ftp-server>>... because of Failed to resolve endpoint: aws-s3://my-dev-bucket?accessKey=ABC***************&secretKey=123abc************** due to: The request signature we calculated does not match the signature you provided. Check your key and signing method.
I then tried to do this the other way. i.e.writing a method like this:
public void boot() throws Exception {
// create a Main instance
main = new Main();
// enable hangup support so you can press ctrl + c to terminate the JVM
main.enableHangupSupport();
// bind MyBean into the registery
main.bind("foo", new MyBean());
// add routes
AWSCredentials awsCredentials = new BasicAWSCredentials("ABC*****************", "123abc*************************");
AmazonS3 client = new AmazonS3Client(awsCredentials);
//main.bind("client", client);
main.addRouteBuilder(new MyRouteBuilder());
main.run();
}
and invoking using the bound variable #client. This approach does not give any exceptions, but the file transfer does not work.
To make sure that there's nothing wrong with my approach, I tried aws-sqs instead of aws-s3 and that works fine (file succesfully transfers to the SQS queue)
Any idea why this is happening? Is there some basic issue with "aws-s3" connector for camel?
Have you tried of using RAW() function to wrap as like RAW(secretkey or accesskey).
It will help you to pass your keys as it is without encoding.
Any plus signs in you secret key need to be url encoded as %2B, in your case **********+*********** becomes **********%2B***********
When you configure Camel endpoints using URIs then the parameter values gets url encoded by default.
This can be a problem when you want to configure passwords as is.
To do that you can tell Camel to use the raw value, by enclosing the value with RAW(value). See more details at How do I configure endpoints which has an example also.
See Camel Documentation
Your url should looks like:
aws-s3:bucketName?accessKey=RAW(XXXX)&secretKey=RAW(XXXX)

NLog to WCF. Closing client throws SocketException on Server

I've been struggling with this problem for a whole day and do not know how to fix it. I have tried various things to resolve the issue but I am at a loss.
I have a project where I am attempting to use the LogReceiverServer from NLog to send and receive messages between 2 PCs. I followed this example here. Everything actually works fine, my WCF service starts up correctly, my client starts up correctly, even the sending of the message to log from client to server works. But, when I shut the client down, I get SocketExceptions thrown by the server for each message that was transmitted. I know this is due to the channel not being closed properly by the client. I cannot find where I must close the channel to prevent the exceptions being thrown by my server. I have read that to manually close the channel I must use
Channel.Close();
would that be correct and where would I put that?
I want to prevent these SocketExceptions. I have found this, but it does not seem to be the correct thing to do. Correct me if I am wrong, but would the solution not use the same principles?
Unless of course I am understanding this completely wrong...
Everything is done using the config files (App.Config and NLog.Config).
Here is my LogReceiverService Target from NLog.config:
<target xsi:type="LogReceiverService"
name="logreceiver"
endpointConfigurationName="LogReceiverClient"
endpointAddress="net.tcp://server:8888/NLogServices/LogReceiverServer/logreceiverserver" />
Here is my endpoint from my app.config:
<endpoint address="net.tcp://server:8888/NLogServices/LogReceiverServer/logreceiverserver"
binding="netTcpBinding"
bindingConfiguration="LogReceiverClient"
contract="NLog.LogReceiverService.ILogReceiverClient"
name="LogReceiverClient" />
Any help or advise would greatly be appreciated.
EDIT: Extended on problem description
OK, So first, here is the Service on my host pretty much as I got it from here:
/// <summary>
/// Log service server object that logs messages.
/// </summary>
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Single)]
public class LogReceiverServer : ILogReceiverServer
{
public void ProcessLogMessages(NLogEvents nevents)
{
var events = nevents.ToEventInfo("Client.");
foreach (var ev in events)
{
var logger = LogManager.GetLogger(ev.LoggerName);
logger.Log(ev);
}
}
}
I then created this class, where I inherit from LogReceiverWebServiceTarget and override protected virtual WcfLogReceiverClient CreateWcfLogReceiverClient(); method. It is exactly the same as is found on GitHub here, except that I registered on the ProcessLogMessagesCompleted event where I close the 'client':
[Target("wcftarget")]
public class WcfTarget : LogReceiverWebServiceTarget
{
protected override WcfLogReceiverClient CreateWcfLogReceiverClient()
{
WcfLogReceiverClient client;
if (string.IsNullOrEmpty(EndpointConfigurationName))
{
// endpoint not specified - use BasicHttpBinding
Binding binding;
if (UseBinaryEncoding)
{
binding = new CustomBinding(new BinaryMessageEncodingBindingElement(), new HttpTransportBindingElement());
}
else
{
binding = new BasicHttpBinding();
}
client = new WcfLogReceiverClient(binding, new EndpointAddress(EndpointAddress));
}
else
{
client = new WcfLogReceiverClient(EndpointConfigurationName, new EndpointAddress(EndpointAddress));
/*commenting this out causes multiple socket exceptions on host*/
client.ProcessLogMessagesCompleted += client_ProcessLogMessagesCompleted;
}
return client;
}
private void client_ProcessLogMessagesCompleted(object sender, AsyncCompletedEventArgs e)
{
WcfLogReceiverClient client = sender as WcfLogReceiverClient;
if (client.State == CommunicationState.Opened)
{
(sender as WcfLogReceiverClient).Close();
}
}
}
The Logger in NLog.config is:
<logger name="*" writeTo="logreceiver" minlevel="Info" />
So then if I try to log like this:
class Program
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private static void Main(string[] args)
{
logger.Info("foo");
}
}
my host gives prints this to Debug:
A first chance exception of type 'System.Net.Sockets.SocketException' occurred in System.dll
A first chance exception of type 'System.ServiceModel.CommunicationException' occurred in System.ServiceModel.dll
Will this have any impact on performance of the host over a long period of time?
The problem has been resolved: https://github.com/NLog/NLog/commit/138fd2ec5d94072a50037a42bc2b84b6910df641

Apache Http Client Put Request Error

I'm trying to upload a file using the Apache Http Client's PUT method. The code is as below;
def putFile(resource: String, file: File): (Int, String) = {
val httpClient = new DefaultHttpClient(connManager)
httpClient.getCredentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(un, pw))
val url = address + "/" + resource
val put = new HttpPut(url)
put.setEntity(new FileEntity(file, "application/xml"))
executeHttp(httpClient, put) match {
case Success(answer) => (answer.getStatusLine.getStatusCode, "Successfully uploaded file")
case Failure(e) => {
e.printStackTrace()
(-1, e.getMessage)
}
}
}
When I tried running the method, I get to see the following error:
org.apache.http.NoHttpResponseException: The target server failed to respond
at org.apache.http.impl.conn.DefaultResponseParser.parseHead(DefaultResponseParser.java:101)
at org.apache.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:252)
at org.apache.http.impl.AbstractHttpClientConnection.receiveResponseHeader(AbstractHttpClientConnection.java:281)
at org.apache.http.impl.conn.DefaultClientConnection.receiveResponseHeader(DefaultClientConnection.java:247)
at org.apache.http.impl.conn.AbstractClientConnAdapter.receiveResponseHeader(AbstractClientConnAdapter.java:219)
at org.apache.http.protocol.HttpRequestExecutor.doReceiveResponse(HttpRequestExecutor.java:298)
at org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:125)
at org.apache.http.impl.client.DefaultRequestDirector.tryExecute(DefaultRequestDirector.java:633)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:454)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:820)
I do not know what has gone wrong? I'm able to do GET requests, but PUT seems not to work! Any clues as to where I should look for?
Look on the server. If GET Works, but PUT does not, then you have to figure out the receiving end.
Also, you may want to write a simple HTML File that has a form with PUT Method in it to rule out your Java Part.
As a sidenode: Its technically possible that something in between stops the request from going through or the response reaching you. Best setup a dummy HTTP Server to do the testing against.
Maybe its also a timeout issue, so the server takes to long to process your PUT.
The connection you are trying to use is a stale connection and therefore the request is failing.
But why are you only seeing an error for the PUT request and you are not seeing it for the GET request?
If you check the DefaultHttpRequestRetryHandler class you will see that by default HttpClient attempts to automatically recover from I/O exceptions. The default auto-recovery mechanism is limited to just a few exceptions that are known to be safe.
HttpClient will make no attempt to recover from any logical or HTTP protocol errors (those derived from HttpException class).
HttpClient will automatically retry those methods that are assumed to be idempotent. Your GET request, but not your PUT request!!
HttpClient will automatically retry those methods that fail with a transport exception while the HTTP request is still being transmitted to the target server (i.e. the request has not been fully transmitted to the server).
This is why you don't notice any error with your GET request, because the retry mechanism handles it.
You should define a CustomHttpRequestRetryHandler extending the DefaultHttpRequestRetryHandler. Something like this:
public class CustomHttpRequestRetryHandler extends DefaultHttpRequestRetryHandler {
#Override
public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
if(exception instanceof NoHttpResponseException) {
return true;
}
return super.retryRequest(exception, executionCount, context);
}
}
Then just assign your CustomHttpRequestRetryHandler
final HttpClientBuilder httpClientBuilder = HttpClients.custom();
httpClientBuilder.setRetryHandler(new CustomHttpRequestRetryHandler());
And that's it, now your PUT request is handled by your new RetryHandler (like the GET was by the default one)

Try to make a Connection Pool with Tomcat 6

I really am having a nightmare configuring Tomcat to set up a connection pool. I have done a lot of reading of various forums and the documents from Tomcat but am having to ask here as a last resort. This is the first time I have tried to get connections from the container so it's all new to me.
I have been having NameNotFoundException's which only seem to be fixed when I put the context.xml file back from MyApp/META-INF/context.xml to Tomcat 6.0/conf/context.xml, so for some reason it's not seeing the context.xml file in MyApp's META-INF directory. Any ideas?
Now I am getting an SQLNestedException: Cannot create PoolableConnectionFactory (''#'localhost' (using password:YES))
First of all it suprises me that the user is blank because I have specified 'root' in the context.xml. As for not being able to create a PoolableConnectionFactory, I have seen a couple of example context.xml files that had a factory attribute. Do I need this? If so what class should I specify there?
My context.xml is:
<?xml version='1.0' encoding='utf-8'?>
<Context>
<!-- Configure a JDBC DataSource for the user database -->
<Resource name="jdbc/searchdb"
type="javax.sql.DataSource"
auth="Container"
user="root"
password="mypassword"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/search"
maxActive="8"
maxIdle="4"/>
<!-- Default set of monitored resources -->
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<!--<WatchedResource>META-INF/context.xml</WatchedResource>-->
</Context>
I have seen a context.xml with a WatchedResource elemnt for the META-INF/context.xml. I tried it but it didn't seem to make a difference and it seems strange to me so I have commented it out. Should I actually be including it?
My test servlet:
package search.web;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import java.sql.*;
import javax.sql.*;
import javax.naming.*;
import search.model.*;
public class ConPoolTest extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
Context ctx = null;
DataSource ds = null;
Connection conn = null;
try {
ctx = new InitialContext();
ds = (DataSource)ctx.lookup("java:comp/env/jdbc/searchdb");
conn = ds.getConnection();
if(conn != null) {
System.out.println("have a connection from the pool");
}
} catch(SQLException e) {
e.printStackTrace();
} catch(NamingException e) {
e.printStackTrace();
} finally {
try {
if(conn!=null) {
conn.close();
}
} catch(SQLException e) {
e.printStackTrace();
}
}
}
}
I look forward to your suggestions.
Many thanks
Joe
PS I would have also listed the stack trace, but for some reason it is showing up in the console, but not in the logs.
Update:
Now that I look at the error message again I'm wondering what it is that is denying me access. I assumed that it was the database, but is itactually the container? Do I need to set up some sort of authentication in the tomcatusers.xml file?
The Tomcat docs for JNDI Datasources contain complete examples how to setup JDBC data sources in the context.xml
Some comments to your question:
Tomcat should copy the context.xml from your app's WAR to conf/Catalina/localhost/app.xml during deployment (when it unpacks your app). The file should not go to conf/. Check whether you have an old copy lying around in these places and clean that up.
The error that it's using the wrong user also suggests that there is more than a single context.xml and you're looking at the wrong one.
You don't need PoolableConnectionFactory with Tomcat 6. This might be cruft left from an update from Tomcat 5 or something broke and they tried several things and forgot to clean up the config file.
Tomcat automatically watches web.xml; there is no need to make it a WatchedResource a second time.