brooklyn-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From henev...@apache.org
Subject [1/2] incubator-brooklyn git commit: Strip "Expect: 100-Continue" header in Jclouds object store
Date Fri, 13 Feb 2015 17:50:42 GMT
Repository: incubator-brooklyn
Updated Branches:
  refs/heads/master 692eb0c87 -> 13f688761


Strip "Expect: 100-Continue" header in Jclouds object store

The 100 Continue response causes the Java 7,8 http client to misbehave,
more so when used in SSL mode.

Sockets closed by GC break the internal bookkeeping
of HttpUrlConnection, leading to invalid handling of the "HTTP/1.1 100 Continue"
response. Coupled with a bug when using SSL sockets reads will block
indefinitely even though a read timeout is explicitly set.


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/17689905
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/17689905
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/17689905

Branch: refs/heads/master
Commit: 176899056f4a599686749724cc207c895aab8539
Parents: 692eb0c
Author: Svetoslav Neykov <svetoslav.neykov@cloudsoftcorp.com>
Authored: Fri Feb 13 17:10:30 2015 +0200
Committer: Svetoslav Neykov <svetoslav.neykov@cloudsoftcorp.com>
Committed: Fri Feb 13 19:27:23 2015 +0200

----------------------------------------------------------------------
 .../brooklyn/location/jclouds/JcloudsUtil.java  |  16 +-
 .../jclouds/JcloudsExpect100ContinueTest.java   | 149 +++++++++++++++++++
 2 files changed, 164 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/17689905/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsUtil.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsUtil.java b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsUtil.java
index 3016058..f617877 100644
--- a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsUtil.java
+++ b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsUtil.java
@@ -30,11 +30,13 @@ import java.io.IOException;
 import java.net.URI;
 import java.util.List;
 import java.util.Map;
+import java.util.Properties;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
 import javax.annotation.Nullable;
 
+import org.jclouds.Constants;
 import org.jclouds.ContextBuilder;
 import org.jclouds.aws.ec2.AWSEC2Api;
 import org.jclouds.blobstore.BlobStoreContext;
@@ -266,13 +268,25 @@ public class JcloudsUtil implements JcloudsLocationConfig {
     public static BlobStoreContext newBlobstoreContext(String provider, @Nullable String
endpoint, String identity, String credential, boolean useSoftlayerFix) {
         AlwaysRetryOnRenew.InterceptRetryOnRenewModule fix = 
             useSoftlayerFix ? new AlwaysRetryOnRenew.InterceptRetryOnRenewModule() : null;
-            
+
+        Properties overrides = new Properties();
+        // * Java 7,8 bug workaround - sockets closed by GC break the internal bookkeeping
+        //   of HttpUrlConnection, leading to invalid handling of the "HTTP/1.1 100 Continue"
+        //   response. Coupled with a bug when using SSL sockets reads will block
+        //   indefinitely even though a read timeout is explicitly set.
+        // * Java 6 ignores the header anyways as it is included in its restricted headers
black list.
+        // * Also there's a bug in SL object store which still expects Content-Length bytes
+        //   even when it responds with a 408 timeout response, leading to incorrectly
+        //   interpreting the next request (triggered by above problem).
+        overrides.setProperty(Constants.PROPERTY_STRIP_EXPECT_HEADER, "true");
+
         ContextBuilder contextBuilder = ContextBuilder.newBuilder(provider).credentials(identity,
credential);
         contextBuilder.modules(MutableList.copyOf(JcloudsUtil.getCommonModules())
             .appendIfNotNull(fix));
         if (!brooklyn.util.text.Strings.isBlank(endpoint)) {
             contextBuilder.endpoint(endpoint);
         }
+        contextBuilder.overrides(overrides);
         BlobStoreContext context = contextBuilder.buildView(BlobStoreContext.class);
 
         if (useSoftlayerFix)

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/17689905/locations/jclouds/src/test/java/brooklyn/entity/rebind/persister/jclouds/JcloudsExpect100ContinueTest.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/test/java/brooklyn/entity/rebind/persister/jclouds/JcloudsExpect100ContinueTest.java
b/locations/jclouds/src/test/java/brooklyn/entity/rebind/persister/jclouds/JcloudsExpect100ContinueTest.java
new file mode 100644
index 0000000..1d5fbde
--- /dev/null
+++ b/locations/jclouds/src/test/java/brooklyn/entity/rebind/persister/jclouds/JcloudsExpect100ContinueTest.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package brooklyn.entity.rebind.persister.jclouds;
+
+import org.jclouds.blobstore.BlobStore;
+import org.jclouds.blobstore.BlobStoreContext;
+import org.jclouds.blobstore.domain.Blob;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.config.BrooklynProperties;
+import brooklyn.entity.basic.Entities;
+import brooklyn.location.jclouds.JcloudsLocation;
+import brooklyn.location.jclouds.JcloudsUtil;
+import brooklyn.management.ManagementContext;
+import brooklyn.test.entity.LocalManagementContextForTests;
+import brooklyn.util.text.Identifiers;
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.ByteSource;
+
+public class JcloudsExpect100ContinueTest {
+    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(JcloudsExpect100ContinueTest.class);
+
+    private static String LOCATION_SPEC = BlobStoreTest.PERSIST_TO_OBJECT_STORE_FOR_TEST_SPEC;
+    private static final String OBJECT_NAME_PUT = "test_put";
+    private static final String OBJECT_NAME_GET = "test_get";
+    
+    private ManagementContext mgmt;
+    private BlobStoreContext context;
+    private String containerName;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        // It's important to disable jclouds debug logging
+        // as it "fixes" the issue.
+        setInfoLevel(Logger.ROOT_LOGGER_NAME);
+        setInfoLevel("jclouds");
+        setInfoLevel("org.jclouds");
+
+        mgmt = new LocalManagementContextForTests(BrooklynProperties.Factory.newDefault());
+        JcloudsLocation jcloudsLocation = (JcloudsLocation) mgmt.getLocationRegistry().resolve(LOCATION_SPEC);
+        context = JcloudsUtil.newBlobstoreContext(
+                jcloudsLocation.getProvider(),
+                jcloudsLocation.getEndpoint(),
+                jcloudsLocation.getIdentity(),
+                jcloudsLocation.getCredential(),
+                false);
+        containerName = BlobStoreTest.CONTAINER_PREFIX+"-"+Identifiers.makeRandomId(8);
+        context.getBlobStore().createContainerInLocation(null, containerName);
+    }
+
+    private void setInfoLevel(String loggerName) {
+        Logger logger = (Logger) LoggerFactory.getLogger(loggerName);
+        logger.setLevel(Level.INFO);
+    }
+    
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        try {
+            context.getBlobStore().deleteContainer(containerName);
+        } catch (Exception e){}
+        context.close();
+        Entities.destroyAll(mgmt);
+    }
+    
+    @Test(groups = "Live", timeOut=240000)
+    public void testPutAfterUnclosedGet() {
+        put(OBJECT_NAME_PUT, getContent());
+        put(OBJECT_NAME_GET, getContent());
+
+        for (int i = 1; i <= 50; i++) {
+            long start = System.currentTimeMillis();
+            get(OBJECT_NAME_GET);
+            long afterGet = System.currentTimeMillis();
+            LOG.info(i + ". GET @" + (afterGet - start));
+
+            System.gc();
+            System.gc();
+            System.gc();
+            sleep(1000);
+
+            // Without excluding Expect: 100-Continue header
+            // the PUT is supposed to block until the server
+            // times out
+
+            long beforePut = System.currentTimeMillis();
+            put(OBJECT_NAME_PUT, getContent());
+            long end = System.currentTimeMillis();
+            LOG.info(i + ". PUT @" + (end - beforePut));
+        }
+    }
+
+    private String getContent() {
+        return "1234567890";
+    }
+
+    private void put(String name, String content) {
+        BlobStore blobStore = context.getBlobStore();
+        byte[] bytes = content.getBytes(Charsets.UTF_8);
+        Blob blob = blobStore.blobBuilder(name)
+                .payload(ByteSource.wrap(bytes))
+                .contentLength(bytes.length)
+                .build();
+        try {
+            blobStore.putBlob(containerName, blob);
+        } catch (Exception e) {
+            LOG.error("PUT " + name + " failed", e);
+        }
+    }
+
+    private Blob get(String name) {
+        try {
+            BlobStore blobStore = context.getBlobStore();
+            return blobStore.getBlob(containerName, name);
+        } catch (Exception e) {
+            LOG.error("GET " + name + " failed", e);
+            return null;
+        }
+    }
+
+    private void sleep(long millis) {
+        try {
+            Thread.sleep(millis);
+        } catch (InterruptedException e) {
+        }
+    }
+
+}


Mime
View raw message