From eadec9ac9ca82ab1b5baea8c69b6ab35b55c452b Mon Sep 17 00:00:00 2001 From: Andrea Turli Date: Wed, 21 Dec 2016 12:03:27 +0100 Subject: [PATCH 1/7] initial work to support HttpEntity - add HttpCommnadEffector - add CompositeEffector - add EntityInitializers util class to resolve DSL injected as params into the HttpCommandEffector --- .../brooklyn/core/effector/AddSensor.java | 2 + .../core/effector/CompositeEffector.java | 130 ++++++++++++++++ .../brooklyn/core/effector/Effectors.java | 4 + .../effector/http/HttpCommandEffector.java | 145 ++++++++++++++++++ .../core/entity/EntityInitializers.java | 32 +++- .../core/sensor/http/HttpRequestSensor.java | 47 +++--- .../CompositeEffectorIntegrationTest.java | 78 ++++++++++ .../HttpCommandEffectorIntegrationTest.java | 125 +++++++++++++++ .../apache/brooklyn/rest/api/EffectorApi.java | 28 ++-- 9 files changed, 559 insertions(+), 32 deletions(-) create mode 100644 core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java create mode 100644 core/src/main/java/org/apache/brooklyn/core/effector/http/HttpCommandEffector.java create mode 100644 core/src/test/java/org/apache/brooklyn/core/effector/CompositeEffectorIntegrationTest.java create mode 100644 core/src/test/java/org/apache/brooklyn/core/effector/http/HttpCommandEffectorIntegrationTest.java diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java b/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java index ba8d679046..92cc4ec140 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java @@ -57,6 +57,7 @@ public class AddSensor implements EntityInitializer { protected final Duration period; protected final String type; protected AttributeSensor sensor; + protected final ConfigBag params; public AddSensor(Map params) { this(ConfigBag.newInstance(params)); @@ -66,6 +67,7 @@ public AddSensor(final ConfigBag params) { this.name = Preconditions.checkNotNull(params.get(SENSOR_NAME), "Name must be supplied when defining a sensor"); this.period = params.get(SENSOR_PERIOD); this.type = params.get(SENSOR_TYPE); + this.params = params; } @Override diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java new file mode 100644 index 0000000000..88cd92caba --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java @@ -0,0 +1,130 @@ +/* + * 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 org.apache.brooklyn.core.effector; + +import java.util.List; +import java.util.Map; + +import javax.annotation.Nullable; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.entity.EntityLocal; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder; +import org.apache.brooklyn.core.entity.EntityInitializers; +import org.apache.brooklyn.core.entity.EntityInternal; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.guava.Maybe; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.reflect.TypeToken; + +@Beta +public class CompositeEffector extends AddEffector { + + private static final Logger LOG = LoggerFactory.getLogger(CompositeEffector.class); + + public static final ConfigKey> EFFECTORS = ConfigKeys.newConfigKey(new TypeToken>() {}, "effectors", + "Effector names to be chained together in the composite effector", ImmutableList.of()); + public static final ConfigKey OVERRIDE = ConfigKeys.newBooleanConfigKey("override", + "Wheter additional defined effectors should override pre-existing effector with same name or not (default: false)", Boolean.FALSE); + public CompositeEffector(ConfigBag params) { + super(newEffectorBuilder(params).build()); + } + + public CompositeEffector(Map params) { + this(ConfigBag.newInstance(params)); + } + + public static EffectorBuilder newEffectorBuilder(ConfigBag params) { + EffectorBuilder eff = AddEffector.newEffectorBuilder(String.class, params); + eff.impl(new Body(eff.buildAbstract(), params)); + return eff; + } + + @Override + public void apply(EntityLocal entity) { + Maybe> effectorMaybe = entity.getEntityType().getEffectorByName(effector.getName()); + if (!effectorMaybe.isAbsentOrNull()) { + Effector original = Effectors.effector(effectorMaybe.get()).name("original-" + effector.getName()).build(); + ((EntityInternal)entity).getMutableEntityType().addEffector(original); + } + super.apply(entity); + } + + protected static class Body extends EffectorBody { + private final Effector effector; + private final ConfigBag params; + + public Body(Effector eff, ConfigBag params) { + this.effector = eff; + Preconditions.checkNotNull(params.getAllConfigRaw().get(EFFECTORS.getName()), "Effector names must be supplied when defining this effector"); + this.params = params; + } + + @Override + public String call(final ConfigBag params) { + ConfigBag allConfig = ConfigBag.newInstanceCopying(this.params).putAll(params); + final List effectorNames = EntityInitializers.resolve(allConfig, EFFECTORS); + final Boolean override = allConfig.get(OVERRIDE); + + List results = Lists.newArrayList(); + if (!override) { + List> originalEffectors = FluentIterable.from(entity().getEntityType().getEffectors()) + .filter(new Predicate>() { + @Override + public boolean apply(@Nullable Effector input) { + return input.getName().equals("original-" + effector.getName()); + } + }) + .toList(); + + for (Effector originalEffector : originalEffectors) { + results.add(invokeEffectorNamed(originalEffector.getName(), params)); + } + } + for (String eff : effectorNames) { + results.add(invokeEffectorNamed(eff, params)); + } + return Iterables.toString(results); + } + + private Object invokeEffectorNamed(String effectorName, ConfigBag params) { + LOG.debug("{} invoking effector on {}, effector={}, parameters={}", + new Object[]{this, entity(), effectorName, params}); + Maybe> effector = entity().getEntityType().getEffectorByName(effectorName); + if (effector.isAbsent()) { + // TODO + } + return entity().invoke(effector.get(), params.getAllConfig()).getUnchecked(); + + } + } + + +} diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/Effectors.java b/core/src/main/java/org/apache/brooklyn/core/effector/Effectors.java index c644001a86..53db25ae82 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/Effectors.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/Effectors.java @@ -64,6 +64,10 @@ private EffectorBuilder(Class returnType, String effectorName) { this.returnType = returnType; this.effectorName = effectorName; } + public EffectorBuilder name(String name) { + this.effectorName = name; + return this; + } public EffectorBuilder description(String description) { this.description = description; return this; diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/http/HttpCommandEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/http/HttpCommandEffector.java new file mode 100644 index 0000000000..7ef2126b76 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/effector/http/HttpCommandEffector.java @@ -0,0 +1,145 @@ +/* + * 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 org.apache.brooklyn.core.effector.http; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URI; +import java.util.Map; +import java.util.concurrent.Callable; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.mgmt.Task; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.config.MapConfigKey; +import org.apache.brooklyn.core.effector.AddEffector; +import org.apache.brooklyn.core.effector.EffectorBody; +import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder; +import org.apache.brooklyn.core.entity.EntityInitializers; +import org.apache.brooklyn.core.sensor.Sensors; +import org.apache.brooklyn.util.collections.Jsonya; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.core.task.Tasks; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.http.executor.HttpConfig; +import org.apache.brooklyn.util.http.executor.HttpExecutor; +import org.apache.brooklyn.util.http.executor.HttpRequest; +import org.apache.brooklyn.util.http.executor.HttpResponse; +import org.apache.brooklyn.util.http.executor.UsernamePassword; +import org.apache.brooklyn.util.http.executor.apacheclient.HttpExecutorImpl; + +import com.google.common.base.Preconditions; +import com.google.common.io.ByteStreams; +import com.jayway.jsonpath.JsonPath; + +public final class HttpCommandEffector extends AddEffector { + + public static final ConfigKey EFFECTOR_URI = ConfigKeys.newStringConfigKey("uri"); + public static final ConfigKey EFFECTOR_HTTP_VERB = ConfigKeys.newStringConfigKey("httpVerb"); + public static final ConfigKey EFFECTOR_HTTP_USERNAME = ConfigKeys.newStringConfigKey("httpUsername"); + public static final ConfigKey EFFECTOR_HTTP_PASSWORD = ConfigKeys.newStringConfigKey("httpPassword"); + public static final ConfigKey> EFFECTOR_HTTP_HEADERS = new MapConfigKey(String.class, "headers"); + public static final ConfigKey> EFFECTOR_HTTP_PAYLOAD = new MapConfigKey(String.class, "httpPayload"); + public static final ConfigKey JSON_PATH = ConfigKeys.newStringConfigKey("jsonPath", "JSON path to select in HTTP response; default $", "$"); + public static final ConfigKey PUBLISH_SENSOR = ConfigKeys.newStringConfigKey("publishSensor", "Sensor name where to store json path extracted value"); + + public HttpCommandEffector(ConfigBag params) { + super(newEffectorBuilder(params).build()); + } + + public static EffectorBuilder newEffectorBuilder(ConfigBag params) { + EffectorBuilder eff = AddEffector.newEffectorBuilder(String.class, params); + eff.impl(new Body(eff.buildAbstract(), params)); + return eff; + } + + protected static class Body extends EffectorBody { + private final Effector effector; + private final ConfigBag params; + + public Body(Effector eff, final ConfigBag params) { + this.effector = eff; + Preconditions.checkNotNull(params.getAllConfigRaw().get(EFFECTOR_URI.getName()), "uri must be supplied when defining this effector"); + Preconditions.checkNotNull(params.getAllConfigRaw().get(EFFECTOR_HTTP_VERB.getName()), "HTTP verb must be supplied when defining this effector"); + this.params = params; + } + + @Override + public String call(final ConfigBag params) { + ConfigBag allConfig = ConfigBag.newInstanceCopying(this.params).putAll(params); + final String uri = EntityInitializers.resolve(allConfig, EFFECTOR_URI); + final String httpVerb = EntityInitializers.resolve(allConfig, EFFECTOR_HTTP_VERB); + final String httpUsername = EntityInitializers.resolve(allConfig, EFFECTOR_HTTP_USERNAME); + final String httpPassword = EntityInitializers.resolve(allConfig, EFFECTOR_HTTP_PASSWORD); + final Map headers = EntityInitializers.resolve(allConfig, EFFECTOR_HTTP_HEADERS); + final Map payload = EntityInitializers.resolve(allConfig, EFFECTOR_HTTP_PAYLOAD); + final String jsonPath = EntityInitializers.resolve(allConfig, JSON_PATH); + final String publishSensor = EntityInitializers.resolve(allConfig, PUBLISH_SENSOR); + Task t = Tasks.builder().displayName(effector.getName()).body(new Callable() { + @Override + public Object call() throws Exception { + HttpExecutor httpExecutor = HttpExecutorImpl.newInstance(); + + String body = ""; + if (payload != null && !payload.isEmpty() && headers.containsKey("Content-Type")) { + body = Jsonya.newInstance().put(payload).toString(); + } + HttpRequest.Builder httpRequestBuilder = new HttpRequest.Builder() + .body(body.getBytes()) + .uri(URI.create(uri)) + .method(httpVerb) + .config(HttpConfig.builder() + .trustSelfSigned(true) + .trustAll(true) + .laxRedirect(true) + .build()); + + if (headers != null) { + httpRequestBuilder.headers(headers); + } + + if (httpUsername != null && httpPassword != null) { + httpRequestBuilder.credentials(new UsernamePassword(httpUsername, httpPassword)); + } + + HttpRequest httpRequest = httpRequestBuilder.build(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + HttpResponse response = httpExecutor.execute(httpRequest); + ByteStreams.copy(response.getContent(), out); + return new String(out.toByteArray()); + } catch (IOException e) { + throw Exceptions.propagate(e); + } + } + }).build(); + + String val = (String) queue(t).getUnchecked(); + if (jsonPath != null) { + String extractedValue = JsonPath.parse(val).read(jsonPath, String.class); + entity().sensors().set(Sensors.newStringSensor(publishSensor), extractedValue); + return extractedValue; + } else { + return val; + } + } + + } +} diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/EntityInitializers.java b/core/src/main/java/org/apache/brooklyn/core/entity/EntityInitializers.java index a258007b5b..cffcced74b 100644 --- a/core/src/main/java/org/apache/brooklyn/core/entity/EntityInitializers.java +++ b/core/src/main/java/org/apache/brooklyn/core/entity/EntityInitializers.java @@ -22,6 +22,11 @@ import org.apache.brooklyn.api.entity.EntityInitializer; import org.apache.brooklyn.api.entity.EntityLocal; +import org.apache.brooklyn.api.mgmt.ExecutionContext; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.core.internal.ConfigKeySelfExtracting; +import org.apache.brooklyn.util.core.task.BasicExecutionContext; import com.google.common.collect.ImmutableList; @@ -41,9 +46,32 @@ public void apply(EntityLocal entity) { } } - public static EntityInitializer addingTags(Object... tags) { return new AddTags(tags); } - + + /** + * Resolves key in the + * {@link BasicExecutionContext#getCurrentExecutionContext current execution context}. + * @see #resolve(ConfigBag, ConfigKey, ExecutionContext) + */ + public static T resolve(ConfigBag configBag, ConfigKey key) { + return resolve(configBag, key, BasicExecutionContext.getCurrentExecutionContext()); + } + + /** + * Gets the value for key from configBag. + *

+ * If key is an instance of {@link ConfigKeySelfExtracting} and executionContext is + * not null then its value will be retrieved per the key's implementation of + * {@link ConfigKeySelfExtracting#extractValue extractValue}. Otherwise, the value + * will be retrieved from configBag directly. + */ + public static T resolve(ConfigBag configBag, ConfigKey key, ExecutionContext executionContext) { + if (key instanceof ConfigKeySelfExtracting && executionContext != null) { + ConfigKeySelfExtracting ckse = ((ConfigKeySelfExtracting) key); + return ckse.extractValue(configBag.getAllConfigAsConfigKeyMap(), executionContext); + } + return configBag.get(key); + } } diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java b/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java index dea44d3644..966a88cdbb 100644 --- a/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java +++ b/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java @@ -19,13 +19,14 @@ package org.apache.brooklyn.core.sensor.http; import java.net.URI; - -import net.minidev.json.JSONObject; +import java.util.Map; import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.config.MapConfigKey; import org.apache.brooklyn.core.effector.AddSensor; +import org.apache.brooklyn.core.entity.EntityInitializers; import org.apache.brooklyn.core.sensor.ssh.SshCommandSensor; import org.apache.brooklyn.feed.http.HttpFeed; import org.apache.brooklyn.feed.http.HttpPollConfig; @@ -38,6 +39,8 @@ import com.google.common.base.Functions; import com.google.common.base.Supplier; +import net.minidev.json.JSONObject; + /** * Configurable {@link org.apache.brooklyn.api.entity.EntityInitializer} which adds an HTTP sensor feed to retrieve the * {@link JSONObject} from a JSON response in order to populate the sensor with the data at the {@code jsonPath}. @@ -53,24 +56,10 @@ public final class HttpRequestSensor extends AddSensor { public static final ConfigKey JSON_PATH = ConfigKeys.newStringConfigKey("jsonPath", "JSON path to select in HTTP response; default $", "$"); public static final ConfigKey USERNAME = ConfigKeys.newStringConfigKey("username", "Username for HTTP request, if required"); public static final ConfigKey PASSWORD = ConfigKeys.newStringConfigKey("password", "Password for HTTP request, if required"); - - protected final Supplier uri; - protected final String jsonPath; - protected final String username; - protected final String password; + public static final ConfigKey> HEADERS = new MapConfigKey(String.class, "headers"); public HttpRequestSensor(final ConfigBag params) { super(params); - - uri = new Supplier() { - @Override - public URI get() { - return URI.create(params.get(SENSOR_URI)); - } - }; - jsonPath = params.get(JSON_PATH); - username = params.get(USERNAME); - password = params.get(PASSWORD); } @Override @@ -81,18 +70,36 @@ public void apply(final EntityLocal entity) { LOG.debug("Adding HTTP JSON sensor {} to {}", name, entity); } + final ConfigBag allConfig = ConfigBag.newInstanceCopying(this.params).putAll(params); + final Supplier uri = new Supplier() { + @Override + public URI get() { + return URI.create(EntityInitializers.resolve(allConfig, SENSOR_URI)); + } + }; + final String jsonPath = EntityInitializers.resolve(allConfig, JSON_PATH); + final String username = EntityInitializers.resolve(allConfig, USERNAME); + final String password = EntityInitializers.resolve(allConfig, PASSWORD); + final Map headers = EntityInitializers.resolve(allConfig, HEADERS); + + HttpPollConfig pollConfig = new HttpPollConfig(sensor) .checkSuccess(HttpValueFunctions.responseCodeEquals(200)) .onFailureOrException(Functions.constant((T) null)) .onSuccess(HttpValueFunctions.jsonContentsFromPath(jsonPath)) .period(period); - HttpFeed feed = HttpFeed.builder().entity(entity) + HttpFeed.Builder httpRequestBuilder = HttpFeed.builder().entity(entity) .baseUri(uri) .credentialsIfNotNull(username, password) - .poll(pollConfig) - .build(); + .poll(pollConfig); + if (headers != null) { + httpRequestBuilder.headers(headers); + } + + HttpFeed feed = httpRequestBuilder.build(); entity.addFeed(feed); } + } diff --git a/core/src/test/java/org/apache/brooklyn/core/effector/CompositeEffectorIntegrationTest.java b/core/src/test/java/org/apache/brooklyn/core/effector/CompositeEffectorIntegrationTest.java new file mode 100644 index 0000000000..e8cc30e04d --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/core/effector/CompositeEffectorIntegrationTest.java @@ -0,0 +1,78 @@ +/* + * 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 org.apache.brooklyn.core.effector; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.entity.EntityLocal; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.core.effector.http.HttpCommandEffector; +import org.apache.brooklyn.core.entity.Entities; +import org.apache.brooklyn.core.test.entity.TestApplication; +import org.apache.brooklyn.core.test.entity.TestEntity; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; + +public class CompositeEffectorIntegrationTest { + + final static Effector EFFECTOR_START = Effectors.effector(String.class, "start").buildAbstract(); + + private TestApplication app; + private EntityLocal entity; + + @BeforeMethod(alwaysRun=true) + public void setUp() throws Exception { + app = TestApplication.Factory.newManagedInstanceForTests(); + entity = app.createAndManageChild(EntitySpec.create(TestEntity.class).location(TestApplication.LOCALHOST_MACHINE_SPEC)); + app.start(ImmutableList.of()); + } + + @AfterMethod(alwaysRun=true) + public void tearDown() throws Exception { + if (app != null) Entities.destroyAll(app.getManagementContext()); + } + + @Test(groups="Integration") + public void testCompositeEffector() throws Exception { + new HttpCommandEffector(ConfigBag.newInstance() + .configure(HttpCommandEffector.EFFECTOR_NAME, "eff1") + .configure(HttpCommandEffector.EFFECTOR_URI, "https://api.github.com/users/apache") + .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "GET")) + .apply(entity); + new HttpCommandEffector(ConfigBag.newInstance() + .configure(HttpCommandEffector.EFFECTOR_NAME, "eff2") + .configure(HttpCommandEffector.EFFECTOR_URI, "https://api.github.com/users/brooklyncentral") + .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "GET")) + .apply(entity); + new CompositeEffector(ConfigBag.newInstance() + .configure(CompositeEffector.EFFECTOR_NAME, "start") + .configure(CompositeEffector.EFFECTORS, ImmutableList.of("eff1", "eff2"))) + .apply(entity); + + String val = entity.invoke(EFFECTOR_START, MutableMap.of()).get(); + // TODO + System.out.println(val); + } + +} diff --git a/core/src/test/java/org/apache/brooklyn/core/effector/http/HttpCommandEffectorIntegrationTest.java b/core/src/test/java/org/apache/brooklyn/core/effector/http/HttpCommandEffectorIntegrationTest.java new file mode 100644 index 0000000000..080e08a36d --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/core/effector/http/HttpCommandEffectorIntegrationTest.java @@ -0,0 +1,125 @@ +/* + * 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 org.apache.brooklyn.core.effector.http; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.entity.EntityLocal; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.core.effector.Effectors; +import org.apache.brooklyn.core.entity.Entities; +import org.apache.brooklyn.core.sensor.Sensors; +import org.apache.brooklyn.core.test.entity.TestApplication; +import org.apache.brooklyn.core.test.entity.TestEntity; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.jayway.jsonpath.JsonPath; + +public class HttpCommandEffectorIntegrationTest { + + final static Effector EFFECTOR_GITHUB_APACHE_ACCOUNT = Effectors.effector(String.class, "GithubApacheAccount").buildAbstract(); + + private TestApplication app; + private EntityLocal entity; + + @BeforeMethod(alwaysRun=true) + public void setUp() throws Exception { + app = TestApplication.Factory.newManagedInstanceForTests(); + entity = app.createAndManageChild(EntitySpec.create(TestEntity.class).location(TestApplication.LOCALHOST_MACHINE_SPEC)); + app.start(ImmutableList.of()); + } + + @AfterMethod(alwaysRun=true) + public void tearDown() throws Exception { + if (app != null) Entities.destroyAll(app.getManagementContext()); + } + + @Test(groups="Integration") + public void testHttpEffector() throws Exception { + new HttpCommandEffector(ConfigBag.newInstance() + .configure(HttpCommandEffector.EFFECTOR_NAME, "GithubApacheAccount") + .configure(HttpCommandEffector.EFFECTOR_URI, "https://api.github.com/users/apache") + .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "GET") + ).apply(entity); + + String val = entity.invoke(EFFECTOR_GITHUB_APACHE_ACCOUNT, MutableMap.of()).get(); + Assert.assertEquals(JsonPath.parse(val).read("$.login", String.class), "apache"); + } + + @Test(groups="Integration") + public void testHttpEffectorWithPayload() throws Exception { + new HttpCommandEffector(ConfigBag.newInstance() + .configure(HttpCommandEffector.EFFECTOR_NAME, "CreateGist") + .configure(HttpCommandEffector.EFFECTOR_URI, "https://api.github.com/gists") + .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "POST") + .configure(HttpCommandEffector.EFFECTOR_HTTP_PAYLOAD, ImmutableMap.of( + "description", "Created via API", + "public", "false", + "files", ImmutableMap.of("demo.txt", ImmutableMap.of("content","Demo")))) + .configure(HttpCommandEffector.EFFECTOR_HTTP_HEADERS, ImmutableMap.of("Content-Type", "application/json")) + .configure(HttpCommandEffector.JSON_PATH, "$.url") + .configure(HttpCommandEffector.PUBLISH_SENSOR, "result") + ).apply(entity); + + String url = entity.invoke(Effectors.effector(String.class, "CreateGist").buildAbstract(), MutableMap.of()).get(); + Assert.assertNotNull(url, "url"); + } + + @Test(groups="Integration") + public void testHttpEffectorWithJsonPath() throws Exception { + new HttpCommandEffector(ConfigBag.newInstance() + .configure(HttpCommandEffector.EFFECTOR_NAME, "GithubApacheAccount") + .configure(HttpCommandEffector.EFFECTOR_URI, "https://api.github.com/users/apache") + .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "GET") + .configure(HttpCommandEffector.JSON_PATH, "$.login") + .configure(HttpCommandEffector.PUBLISH_SENSOR, "result") + ).apply(entity); + + String val = entity.invoke(EFFECTOR_GITHUB_APACHE_ACCOUNT, MutableMap.of()).get(); + Assert.assertEquals(val, "apache"); + Assert.assertEquals(entity.sensors().get(Sensors.newStringSensor("result")), "apache"); + } + + @Test(groups="Integration") + public void testHttpEffectorWithParameters() throws Exception { + new HttpCommandEffector(ConfigBag.newInstance() + .configure(HttpCommandEffector.EFFECTOR_NAME, "GithubApacheAccount") + .configure(HttpCommandEffector.EFFECTOR_URI, "https://api.github.com/users/$user") + .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "GET") + .configure(HttpCommandEffector.EFFECTOR_PARAMETER_DEFS, + MutableMap.of("user", MutableMap.of("defaultValue", "apache")))) + .apply(entity); + + String val; + // explicit value + val = entity.invoke(EFFECTOR_GITHUB_APACHE_ACCOUNT, MutableMap.of("user", "github")).get(); + Assert.assertEquals(JsonPath.parse(val).read("$.login", String.class), "github"); + + // default value + val = entity.invoke(EFFECTOR_GITHUB_APACHE_ACCOUNT, MutableMap.of()).get(); + Assert.assertEquals(JsonPath.parse(val).read("$.login", String.class), "apache"); + } +} diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EffectorApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EffectorApi.java index 2865223f10..6143b6fd23 100644 --- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EffectorApi.java +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EffectorApi.java @@ -18,19 +18,27 @@ */ package org.apache.brooklyn.rest.api; -import io.swagger.annotations.Api; -import org.apache.brooklyn.rest.domain.EffectorSummary; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import java.util.List; +import java.util.Map; import javax.validation.Valid; -import javax.ws.rs.*; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import java.util.List; -import java.util.Map; + +import org.apache.brooklyn.rest.domain.EffectorSummary; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; @Path("/applications/{application}/entities/{entity}/effectors") @Api("Entity Effectors") @@ -54,7 +62,7 @@ public List list( @POST @Path("/{effector}") @ApiOperation(value = "Trigger an effector", - notes="Returns the return value (status 200) if it completes, or an activity task ID (status 202) if it times out") + notes="Returns the return value (status 200) if it completes, or an activity task ID (status 202) if it times out", response = String.class) @ApiResponses(value = { @ApiResponse(code = 404, message = "Could not find application, entity or effector") }) From 119ba5a47b4a5198ae99927511e35107d2ebce89 Mon Sep 17 00:00:00 2001 From: Andrea Turli Date: Mon, 23 Jan 2017 17:12:14 +0100 Subject: [PATCH 2/7] fix CompositeEffector logic --- .../core/effector/CompositeEffector.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java index 88cd92caba..ca627bbae4 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java @@ -36,9 +36,9 @@ import org.slf4j.LoggerFactory; import com.google.common.annotations.Beta; +import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; -import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; @@ -95,19 +95,22 @@ public String call(final ConfigBag params) { List results = Lists.newArrayList(); if (!override) { - List> originalEffectors = FluentIterable.from(entity().getEntityType().getEffectors()) - .filter(new Predicate>() { + Optional> effectorOptional = Iterables.tryFind(entity().getEntityType().getEffectors(), new Predicate>() { @Override public boolean apply(@Nullable Effector input) { return input.getName().equals("original-" + effector.getName()); } - }) - .toList(); - - for (Effector originalEffector : originalEffectors) { - results.add(invokeEffectorNamed(originalEffector.getName(), params)); + }); + // if it is a stop effector, it has to be executed as last effector + if (effectorOptional.isPresent()) { + if (effectorOptional.get().getName().endsWith("-stop")) { + effectorNames.add(effectorOptional.get().getName()); + } else { + effectorNames.add(0, effectorOptional.get().getName()); + } } } + for (String eff : effectorNames) { results.add(invokeEffectorNamed(eff, params)); } From 3d318e4e78085519233bcc763e22320ca40403df Mon Sep 17 00:00:00 2001 From: Andrea Turli Date: Mon, 23 Jan 2017 17:24:31 +0100 Subject: [PATCH 3/7] remove default value for JSON_PATH - some http calls may not require to parse the output --- .../apache/brooklyn/core/effector/http/HttpCommandEffector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/http/HttpCommandEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/http/HttpCommandEffector.java index 7ef2126b76..d2130dd8b9 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/http/HttpCommandEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/http/HttpCommandEffector.java @@ -57,7 +57,7 @@ public final class HttpCommandEffector extends AddEffector { public static final ConfigKey EFFECTOR_HTTP_PASSWORD = ConfigKeys.newStringConfigKey("httpPassword"); public static final ConfigKey> EFFECTOR_HTTP_HEADERS = new MapConfigKey(String.class, "headers"); public static final ConfigKey> EFFECTOR_HTTP_PAYLOAD = new MapConfigKey(String.class, "httpPayload"); - public static final ConfigKey JSON_PATH = ConfigKeys.newStringConfigKey("jsonPath", "JSON path to select in HTTP response; default $", "$"); + public static final ConfigKey JSON_PATH = ConfigKeys.newStringConfigKey("jsonPath", "JSON path to select in HTTP response"); public static final ConfigKey PUBLISH_SENSOR = ConfigKeys.newStringConfigKey("publishSensor", "Sensor name where to store json path extracted value"); public HttpCommandEffector(ConfigBag params) { From b0b83da785944b3929866a89470b52cd8215684f Mon Sep 17 00:00:00 2001 From: Andrea Turli Date: Tue, 31 Jan 2017 16:51:05 +0100 Subject: [PATCH 4/7] add more tests for HttpCommandEffector - add unit test (HttpCommandEffectorTest) - add YamlTest (HttpCommandEffectorYamlTest) - add YamlRebindTest (HttpCommandEffectorYamlRebindTest) --- .../camp/brooklyn/AbstractYamlRebindTest.java | 21 +- .../HttpCommandEffectorYamlRebindTest.java | 92 +++++++++ .../brooklyn/HttpCommandEffectorYamlTest.java | 79 ++++++++ .../core/effector/CompositeEffector.java | 16 +- .../effector/http/HttpCommandEffector.java | 105 +++++++--- .../HttpCommandEffectorIntegrationTest.java | 42 ++-- .../http/HttpCommandEffectorTest.java | 188 ++++++++++++++++++ .../brooklyn/core/effector/http/login.json | 16 ++ 8 files changed, 490 insertions(+), 69 deletions(-) create mode 100644 camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/HttpCommandEffectorYamlRebindTest.java create mode 100644 camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/HttpCommandEffectorYamlTest.java create mode 100644 core/src/test/java/org/apache/brooklyn/core/effector/http/HttpCommandEffectorTest.java create mode 100644 core/src/test/resources/org/apache/brooklyn/core/effector/http/login.json diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlRebindTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlRebindTest.java index 9322866944..9168b25f58 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlRebindTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlRebindTest.java @@ -29,9 +29,12 @@ import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.mgmt.Task; +<<<<<<< 3d318e4e78085519233bcc763e22320ca40403df import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatform; import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherNoServer; import org.apache.brooklyn.camp.brooklyn.spi.creation.CampTypePlanTransformer; +======= +>>>>>>> add more tests for HttpCommandEffector import org.apache.brooklyn.camp.spi.Assembly; import org.apache.brooklyn.camp.spi.AssemblyTemplate; import org.apache.brooklyn.core.catalog.internal.CatalogUtils; @@ -59,13 +62,13 @@ public class AbstractYamlRebindTest extends RebindTestFixture { - private static final Logger LOG = LoggerFactory.getLogger(AbstractYamlTest.class); + private static final Logger LOG = LoggerFactory.getLogger(AbstractYamlRebindTest.class); protected static final String TEST_VERSION = "0.1.2"; protected BrooklynCampPlatform platform; protected BrooklynCampPlatformLauncherNoServer launcher; private boolean forceUpdate; - + @BeforeMethod(alwaysRun = true) @Override public void setUp() throws Exception { @@ -106,7 +109,7 @@ protected LocalManagementContext newMgmtContext() { } return result; } - + @Override protected StartableApplication createApp() { return null; @@ -116,11 +119,11 @@ protected StartableApplication createApp() { protected ManagementContext mgmt() { return (newManagementContext != null) ? newManagementContext : origManagementContext; } - + /////////////////////////////////////////////////// // TODO code below is duplicate of AbstractYamlTest /////////////////////////////////////////////////// - + protected void waitForApplicationTasks(Entity app) { Set> tasks = BrooklynTaskTags.getTasksInEntityContext(mgmt().getExecutionManager(), app); getLogger().info("Waiting on " + tasks.size() + " task(s)"); @@ -149,7 +152,7 @@ protected Entity createAndStartApplication(Reader input) throws Exception { protected Entity createAndStartApplication(String... multiLineYaml) throws Exception { return createAndStartApplication(joinLines(multiLineYaml)); } - + protected Entity createAndStartApplication(String input) throws Exception { return createAndStartApplication(input, MutableMap.of()); } @@ -168,18 +171,18 @@ protected Entity createAndStartApplication(String input, Map startPara protected Entity createStartWaitAndLogApplication(String... input) throws Exception { return createStartWaitAndLogApplication(joinLines(input)); } - + protected Entity createStartWaitAndLogApplication(String input) throws Exception { return createStartWaitAndLogApplication(new StringReader(input)); } - + protected Entity createStartWaitAndLogApplication(Reader input) throws Exception { Entity app = createAndStartApplication(input); waitForApplicationTasks(app); getLogger().info("App started:"); Entities.dumpInfo(app); - + return app; } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/HttpCommandEffectorYamlRebindTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/HttpCommandEffectorYamlRebindTest.java new file mode 100644 index 0000000000..df57bcbeb8 --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/HttpCommandEffectorYamlRebindTest.java @@ -0,0 +1,92 @@ +/* + * 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 org.apache.brooklyn.camp.brooklyn; + +import static org.apache.brooklyn.test.Asserts.assertFalse; +import static org.testng.Assert.assertEquals; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.core.effector.http.HttpCommandEffector; +import org.apache.brooklyn.core.entity.EntityPredicates; +import org.apache.brooklyn.core.entity.StartableApplication; +import org.apache.brooklyn.core.test.entity.TestEntity; +import org.apache.brooklyn.util.guava.Maybe; +import org.testng.annotations.Test; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; + +@Test +public class HttpCommandEffectorYamlRebindTest extends AbstractYamlRebindTest { + + private final static String appId = "my-app-with-http-effector"; + private final static String appVersion = "1.0.0-SNAPSHOT"; + static final String appVersionedId = appId + ":" + appVersion; + + static final String catalogYamlSimple = Joiner.on("\n").join( + "brooklyn.catalog:", + " id: " + appId, + " version: " + appVersion, + " itemType: entity", + " item:", + " type: " + TestEntity.class.getName(), + " name: targetEntity", + " brooklyn.initializers:", + " - type: " + HttpCommandEffector.class.getName(), + " brooklyn.config:", + " name: myEffector", + " description: myDescription", + " uri: https://httpbin.org/get?id=myId", + " httpVerb: GET", + " jsonPath: $.args.id", + " publishSensor: results"); + + @Test + public void testRebindWhenHealthy() throws Exception { + runRebindWhenIsUp(catalogYamlSimple, appVersionedId); + } + + protected void runRebindWhenIsUp(String catalogYaml, String appId) throws Exception { + addCatalogItems(catalogYaml); + + String appYaml = Joiner.on("\n").join( + "services: ", + "- type: " + appId); + createStartWaitAndLogApplication(appYaml); + + // Rebind + StartableApplication newApp = rebind(); + TestEntity testEntity = (TestEntity) Iterables.find(newApp.getChildren(), EntityPredicates.displayNameEqualTo("targetEntity")); + Effector effector = assertHasInitializers(testEntity, "myEffector"); + + // Confirm HttpCommandEffector still functions + Object result = testEntity.invoke(effector, ImmutableMap.of()).get(); + assertEquals(((String)result).trim(), "myId"); + } + + + protected static Effector assertHasInitializers(Entity entity, String effectorName) { + Maybe> effectorMaybe = entity.getEntityType().getEffectorByName(effectorName); + assertFalse(effectorMaybe.isAbsent()); + return effectorMaybe.get(); + } + +} diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/HttpCommandEffectorYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/HttpCommandEffectorYamlTest.java new file mode 100644 index 0000000000..b36771cf7f --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/HttpCommandEffectorYamlTest.java @@ -0,0 +1,79 @@ +/* + * 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 org.apache.brooklyn.camp.brooklyn; + +import static org.testng.Assert.assertEquals; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.core.effector.http.HttpCommandEffector; +import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; + +public class HttpCommandEffectorYamlTest extends AbstractYamlTest { + private static final Logger log = LoggerFactory.getLogger(HttpCommandEffectorYamlTest.class); + + @Test + public void testHttpCommandEffectorWithParameters() throws Exception { + Entity app = createAndStartApplication( + "location: localhost", + "services:", + "- type: " + EmptySoftwareProcess.class.getName(), + " brooklyn.config:", + " onbox.base.dir.skipResolution: true", + " softwareProcess.serviceProcessIsRunningPollPeriod: forever", + " brooklyn.initializers:", + " - type: " + HttpCommandEffector.class.getName(), + " brooklyn.config:", + " name: myEffector", + " description: myDescription", + " uri: https://httpbin.org/get?id=myId", + " httpVerb: GET", + " jsonPath: $.args.id", + " publishSensor: results" + ); + waitForApplicationTasks(app); + + EmptySoftwareProcess entity = (EmptySoftwareProcess) Iterables.getOnlyElement(app.getChildren()); + Effector effector = entity.getEntityType().getEffectorByName("myEffector").get(); + + // Invoke with parameters + { + Object result = entity.invoke(effector, ImmutableMap.of("uri", "https://httpbin.org/get?pwd=passwd", "jsonPath", "$.args.pwd")).get(); + assertEquals(((String)result).trim(), "passwd"); + + } + + // Invoke with default parameter + { + Object result = entity.invoke(effector, ImmutableMap.of()).get(); + assertEquals(((String)result).trim(), "myId"); + } + } + + @Override + protected Logger getLogger() { + return log; + } +} diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java index ca627bbae4..32f0f1dfab 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java @@ -93,14 +93,14 @@ public String call(final ConfigBag params) { final List effectorNames = EntityInitializers.resolve(allConfig, EFFECTORS); final Boolean override = allConfig.get(OVERRIDE); - List results = Lists.newArrayList(); + List results = Lists.newArrayList(); if (!override) { Optional> effectorOptional = Iterables.tryFind(entity().getEntityType().getEffectors(), new Predicate>() { - @Override - public boolean apply(@Nullable Effector input) { - return input.getName().equals("original-" + effector.getName()); - } - }); + @Override + public boolean apply(@Nullable Effector input) { + return input.getName().equals("original-" + effector.getName()); + } + }); // if it is a stop effector, it has to be executed as last effector if (effectorOptional.isPresent()) { if (effectorOptional.get().getName().endsWith("-stop")) { @@ -110,7 +110,7 @@ public boolean apply(@Nullable Effector input) { } } } - + for (String eff : effectorNames) { results.add(invokeEffectorNamed(eff, params)); } @@ -125,7 +125,7 @@ private Object invokeEffectorNamed(String effectorName, ConfigBag params) { // TODO } return entity().invoke(effector.get(), params.getAllConfig()).getUnchecked(); - + } } diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/http/HttpCommandEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/http/HttpCommandEffector.java index d2130dd8b9..2d8266e581 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/http/HttpCommandEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/http/HttpCommandEffector.java @@ -18,9 +18,14 @@ */ package org.apache.brooklyn.core.effector.http; +import static com.google.common.base.Preconditions.checkArgument; + import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.net.MalformedURLException; import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; import java.util.Map; import java.util.concurrent.Callable; @@ -45,8 +50,12 @@ import org.apache.brooklyn.util.http.executor.UsernamePassword; import org.apache.brooklyn.util.http.executor.apacheclient.HttpExecutorImpl; +import com.google.common.base.Enums; +import com.google.common.base.Joiner; +import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.io.ByteStreams; +import com.google.common.net.HttpHeaders; import com.jayway.jsonpath.JsonPath; public final class HttpCommandEffector extends AddEffector { @@ -60,6 +69,10 @@ public final class HttpCommandEffector extends AddEffector { public static final ConfigKey JSON_PATH = ConfigKeys.newStringConfigKey("jsonPath", "JSON path to select in HTTP response"); public static final ConfigKey PUBLISH_SENSOR = ConfigKeys.newStringConfigKey("publishSensor", "Sensor name where to store json path extracted value"); + private enum HttpVerb { + GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE; + } + public HttpCommandEffector(ConfigBag params) { super(newEffectorBuilder(params).build()); } @@ -69,7 +82,7 @@ public static EffectorBuilder newEffectorBuilder(ConfigBag params) { eff.impl(new Body(eff.buildAbstract(), params)); return eff; } - + protected static class Body extends EffectorBody { private final Effector effector; private final ConfigBag params; @@ -97,32 +110,12 @@ public String call(final ConfigBag params) { public Object call() throws Exception { HttpExecutor httpExecutor = HttpExecutorImpl.newInstance(); - String body = ""; - if (payload != null && !payload.isEmpty() && headers.containsKey("Content-Type")) { - body = Jsonya.newInstance().put(payload).toString(); - } - HttpRequest.Builder httpRequestBuilder = new HttpRequest.Builder() - .body(body.getBytes()) - .uri(URI.create(uri)) - .method(httpVerb) - .config(HttpConfig.builder() - .trustSelfSigned(true) - .trustAll(true) - .laxRedirect(true) - .build()); - - if (headers != null) { - httpRequestBuilder.headers(headers); - } - - if (httpUsername != null && httpPassword != null) { - httpRequestBuilder.credentials(new UsernamePassword(httpUsername, httpPassword)); - } - - HttpRequest httpRequest = httpRequestBuilder.build(); + String body = getBodyFromPayload(payload, headers); + HttpRequest request = buildHttpRequest(httpVerb, uri, headers, httpUsername, httpPassword, body); ByteArrayOutputStream out = new ByteArrayOutputStream(); try { - HttpResponse response = httpExecutor.execute(httpRequest); + HttpResponse response = httpExecutor.execute(request); + validateResponse(response); ByteStreams.copy(response.getContent(), out); return new String(out.toByteArray()); } catch (IOException e) { @@ -131,14 +124,64 @@ public Object call() throws Exception { } }).build(); - String val = (String) queue(t).getUnchecked(); - if (jsonPath != null) { - String extractedValue = JsonPath.parse(val).read(jsonPath, String.class); + String responseBody = (String) queue(t).getUnchecked(); + + if (jsonPath == null) return responseBody; + String extractedValue = JsonPath.parse(responseBody).read(jsonPath, String.class); + if (publishSensor != null) { entity().sensors().set(Sensors.newStringSensor(publishSensor), extractedValue); - return extractedValue; - } else { - return val; } + return extractedValue; + } + + private void validateResponse(HttpResponse response) { + int statusCode = response.code(); + if (statusCode == 401) { + throw new RuntimeException("Authorization exception"); + } else if (statusCode == 404) { + throw new RuntimeException("Resource not found"); + } else if (statusCode >= 500) { + throw new RuntimeException("Server error"); + } + } + + private HttpRequest buildHttpRequest(String httpVerb, String url, Map headers, String httpUsername, String httpPassword, String body) throws MalformedURLException, URISyntaxException { + // validate url string + URI uri = new URL(url).toURI(); + // validate HTTP verb + validateHttpVerb(httpVerb); + HttpRequest.Builder httpRequestBuilder = new HttpRequest.Builder() + .body(body.getBytes()) + .uri(uri) + .method(httpVerb) + .config(HttpConfig.builder() + .trustSelfSigned(true) + .trustAll(true) + .laxRedirect(true) + .build()); + + if (headers != null) { + httpRequestBuilder.headers(headers); + } + + if (httpUsername != null && httpPassword != null) { + httpRequestBuilder.credentials(new UsernamePassword(httpUsername, httpPassword)); + } + + return httpRequestBuilder.build(); + } + + private void validateHttpVerb(String httpVerb) { + Optional state = Enums.getIfPresent(HttpVerb.class, httpVerb.toUpperCase()); + checkArgument(state.isPresent(), "Expected one of %s but was %s", Joiner.on(',').join(HttpVerb.values()), httpVerb); + } + + private String getBodyFromPayload(Map payload, Map headers) { + String body = ""; + if (payload != null && !payload.isEmpty() && headers.containsKey(HttpHeaders.CONTENT_TYPE)) { + body = Jsonya.newInstance().put(payload).toString(); + } + return body; } } diff --git a/core/src/test/java/org/apache/brooklyn/core/effector/http/HttpCommandEffectorIntegrationTest.java b/core/src/test/java/org/apache/brooklyn/core/effector/http/HttpCommandEffectorIntegrationTest.java index 080e08a36d..7d1a91a916 100644 --- a/core/src/test/java/org/apache/brooklyn/core/effector/http/HttpCommandEffectorIntegrationTest.java +++ b/core/src/test/java/org/apache/brooklyn/core/effector/http/HttpCommandEffectorIntegrationTest.java @@ -40,7 +40,7 @@ public class HttpCommandEffectorIntegrationTest { - final static Effector EFFECTOR_GITHUB_APACHE_ACCOUNT = Effectors.effector(String.class, "GithubApacheAccount").buildAbstract(); + final static Effector EFFECTOR_HTTPBIN = Effectors.effector(String.class, "Httpbin").buildAbstract(); private TestApplication app; private EntityLocal entity; @@ -60,20 +60,20 @@ public void tearDown() throws Exception { @Test(groups="Integration") public void testHttpEffector() throws Exception { new HttpCommandEffector(ConfigBag.newInstance() - .configure(HttpCommandEffector.EFFECTOR_NAME, "GithubApacheAccount") - .configure(HttpCommandEffector.EFFECTOR_URI, "https://api.github.com/users/apache") + .configure(HttpCommandEffector.EFFECTOR_NAME, "Httpbin") + .configure(HttpCommandEffector.EFFECTOR_URI, "https://httpbin.org/get?login=myLogin") .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "GET") ).apply(entity); - String val = entity.invoke(EFFECTOR_GITHUB_APACHE_ACCOUNT, MutableMap.of()).get(); - Assert.assertEquals(JsonPath.parse(val).read("$.login", String.class), "apache"); + String val = entity.invoke(EFFECTOR_HTTPBIN, MutableMap.of()).get(); + Assert.assertEquals(JsonPath.parse(val).read("$.args.login", String.class), "myLogin"); } @Test(groups="Integration") public void testHttpEffectorWithPayload() throws Exception { new HttpCommandEffector(ConfigBag.newInstance() - .configure(HttpCommandEffector.EFFECTOR_NAME, "CreateGist") - .configure(HttpCommandEffector.EFFECTOR_URI, "https://api.github.com/gists") + .configure(HttpCommandEffector.EFFECTOR_NAME, "HttpbinPost") + .configure(HttpCommandEffector.EFFECTOR_URI, "https://httpbin.org/post") .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "POST") .configure(HttpCommandEffector.EFFECTOR_HTTP_PAYLOAD, ImmutableMap.of( "description", "Created via API", @@ -84,42 +84,42 @@ public void testHttpEffectorWithPayload() throws Exception { .configure(HttpCommandEffector.PUBLISH_SENSOR, "result") ).apply(entity); - String url = entity.invoke(Effectors.effector(String.class, "CreateGist").buildAbstract(), MutableMap.of()).get(); + String url = entity.invoke(Effectors.effector(String.class, "HttpbinPost").buildAbstract(), MutableMap.of()).get(); Assert.assertNotNull(url, "url"); } @Test(groups="Integration") public void testHttpEffectorWithJsonPath() throws Exception { new HttpCommandEffector(ConfigBag.newInstance() - .configure(HttpCommandEffector.EFFECTOR_NAME, "GithubApacheAccount") - .configure(HttpCommandEffector.EFFECTOR_URI, "https://api.github.com/users/apache") + .configure(HttpCommandEffector.EFFECTOR_NAME, "Httpbin") + .configure(HttpCommandEffector.EFFECTOR_URI, "https://httpbin.org/get?id=myId") .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "GET") - .configure(HttpCommandEffector.JSON_PATH, "$.login") + .configure(HttpCommandEffector.JSON_PATH, "$.args.id") .configure(HttpCommandEffector.PUBLISH_SENSOR, "result") ).apply(entity); - String val = entity.invoke(EFFECTOR_GITHUB_APACHE_ACCOUNT, MutableMap.of()).get(); - Assert.assertEquals(val, "apache"); - Assert.assertEquals(entity.sensors().get(Sensors.newStringSensor("result")), "apache"); + String val = entity.invoke(EFFECTOR_HTTPBIN, MutableMap.of()).get(); + Assert.assertEquals(val, "myId"); + Assert.assertEquals(entity.sensors().get(Sensors.newStringSensor("result")), "myId"); } @Test(groups="Integration") public void testHttpEffectorWithParameters() throws Exception { new HttpCommandEffector(ConfigBag.newInstance() - .configure(HttpCommandEffector.EFFECTOR_NAME, "GithubApacheAccount") - .configure(HttpCommandEffector.EFFECTOR_URI, "https://api.github.com/users/$user") + .configure(HttpCommandEffector.EFFECTOR_NAME, "Httpbin") + .configure(HttpCommandEffector.EFFECTOR_URI, "https://httpbin.org/get") .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "GET") .configure(HttpCommandEffector.EFFECTOR_PARAMETER_DEFS, - MutableMap.of("user", MutableMap.of("defaultValue", "apache")))) + MutableMap.of("uri", MutableMap.of("defaultValue", "https://httpbin.org/get")))) .apply(entity); String val; // explicit value - val = entity.invoke(EFFECTOR_GITHUB_APACHE_ACCOUNT, MutableMap.of("user", "github")).get(); - Assert.assertEquals(JsonPath.parse(val).read("$.login", String.class), "github"); + val = entity.invoke(EFFECTOR_HTTPBIN, MutableMap.of("uri", "https://httpbin.org/ip")).get(); + Assert.assertNotNull(JsonPath.parse(val).read("$.origin", String.class)); // default value - val = entity.invoke(EFFECTOR_GITHUB_APACHE_ACCOUNT, MutableMap.of()).get(); - Assert.assertEquals(JsonPath.parse(val).read("$.login", String.class), "apache"); + val = entity.invoke(EFFECTOR_HTTPBIN, MutableMap.of()).get(); + Assert.assertEquals(JsonPath.parse(val).read("$.url", String.class), "https://httpbin.org/get"); } } diff --git a/core/src/test/java/org/apache/brooklyn/core/effector/http/HttpCommandEffectorTest.java b/core/src/test/java/org/apache/brooklyn/core/effector/http/HttpCommandEffectorTest.java new file mode 100644 index 0000000000..a4b8ee04cc --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/core/effector/http/HttpCommandEffectorTest.java @@ -0,0 +1,188 @@ +/* + * 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 org.apache.brooklyn.core.effector.http; + +import static org.apache.brooklyn.test.Asserts.assertNotNull; +import static org.testng.Assert.assertEquals; + +import java.io.IOException; +import java.net.URL; +import java.util.concurrent.ExecutionException; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.core.effector.Effectors; +import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; +import org.apache.brooklyn.core.test.entity.TestEntity; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.core.http.BetterMockWebServer; +import org.apache.brooklyn.util.exceptions.PropagatedRuntimeException; +import org.apache.brooklyn.util.time.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.base.Charsets; +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableMap; +import com.google.common.io.Resources; +import com.google.mockwebserver.MockResponse; +import com.google.mockwebserver.RecordedRequest; + +public class HttpCommandEffectorTest extends BrooklynAppUnitTestSupport { + + private static final Logger log = LoggerFactory.getLogger(HttpCommandEffectorTest.class); + private static final String DEFAULT_ENDPOINT = "/"; + + final static Effector EFFECTOR_HTTP_COMMAND = Effectors.effector(String.class, "http-command-effector").buildAbstract(); + + protected BetterMockWebServer server; + protected URL baseUrl; + + protected Location loc; + protected HttpCommandEffector httpCommandEffector; + + @BeforeMethod + public void start() throws IOException { + server = BetterMockWebServer.newInstanceLocalhost(); + server.play(); + } + + @AfterMethod(alwaysRun = true) + public void stop() throws IOException { + server.shutdown(); + } + + protected String url(String path) { + return server.getUrl(path).toString(); + } + + protected MockResponse jsonResponse(String resource) { + return new MockResponse().addHeader("Content-Type", "application/json").setBody(stringFromResource(resource)); + } + + protected MockResponse response404() { + return new MockResponse().setStatus("HTTP/1.1 404 Not Found"); + } + + protected String stringFromResource(String resourceName) { + return stringFromResource("/org/apache/brooklyn/core/effector/http", resourceName); + } + + private String stringFromResource(String prefix, String resourceName) { + try { + return Resources.toString(getClass().getResource(String.format("%s/%s", prefix, resourceName)), Charsets.UTF_8) + .replace(DEFAULT_ENDPOINT, url("")); + } catch (IOException e) { + throw Throwables.propagate(e); + } + } + + protected RecordedRequest assertSent(BetterMockWebServer server, String method, String path) throws InterruptedException { + RecordedRequest request = server.takeRequest(); + assertEquals(request.getMethod(), method); + assertEquals(request.getPath(), path); + return request; + } + + @Test(expectedExceptions = NullPointerException.class) + public void testMissingURI() { + httpCommandEffector = new HttpCommandEffector(ConfigBag.newInstance() + .configure(HttpCommandEffector.EFFECTOR_NAME, EFFECTOR_HTTP_COMMAND.getName()) + .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "GET") + ); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testMissingVerb() { + httpCommandEffector = new HttpCommandEffector(ConfigBag.newInstance() + .configure(HttpCommandEffector.EFFECTOR_NAME, EFFECTOR_HTTP_COMMAND.getName()) + .configure(HttpCommandEffector.EFFECTOR_URI, url("")) + ); + } + + @Test(expectedExceptions = ExecutionException.class) + public void testInvalidURI() throws ExecutionException, InterruptedException { + httpCommandEffector = new HttpCommandEffector(ConfigBag.newInstance() + .configure(HttpCommandEffector.EFFECTOR_NAME, EFFECTOR_HTTP_COMMAND.getName()) + .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "GET") + .configure(HttpCommandEffector.EFFECTOR_URI, "invalid-uri") + ); + TestEntity testEntity = app.createAndManageChild(buildEntitySpec(httpCommandEffector)); + testEntity.invoke(EFFECTOR_HTTP_COMMAND, MutableMap.of()).get(); + } + + @Test(expectedExceptions = ExecutionException.class) + public void testInvalidVerb() throws ExecutionException, InterruptedException { + httpCommandEffector = new HttpCommandEffector(ConfigBag.newInstance() + .configure(HttpCommandEffector.EFFECTOR_NAME, EFFECTOR_HTTP_COMMAND.getName()) + .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "CHANGE") + .configure(HttpCommandEffector.EFFECTOR_URI, url("")) + ); + TestEntity testEntity = app.createAndManageChild(buildEntitySpec(httpCommandEffector)); + testEntity.invoke(EFFECTOR_HTTP_COMMAND, MutableMap.of()).get(); + } + + @Test + public void testHappyPath() throws InterruptedException { + server.enqueue(jsonResponse("login.json")); + + httpCommandEffector = new HttpCommandEffector(ConfigBag.newInstance() + .configure(HttpCommandEffector.EFFECTOR_NAME, EFFECTOR_HTTP_COMMAND.getName()) + .configure(HttpCommandEffector.EFFECTOR_URI, url("/get?login=myLogin")) + .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "GET") + .configure(HttpCommandEffector.JSON_PATH, "$.args.login") + ); + assertNotNull(httpCommandEffector); + TestEntity testEntity = app.createAndManageChild(buildEntitySpec(httpCommandEffector)); + Object output = testEntity.invoke(EFFECTOR_HTTP_COMMAND, ImmutableMap.of()).getUnchecked(Duration.seconds(1)); + assertEquals(output, "myLogin"); + + assertEquals(server.getRequestCount(), 1); + assertSent(server, "GET", "/get?login=myLogin"); + } + + @Test(expectedExceptions = PropagatedRuntimeException.class) + public void testWhen404() throws InterruptedException { + server.enqueue(response404()); + + httpCommandEffector = new HttpCommandEffector(ConfigBag.newInstance() + .configure(HttpCommandEffector.EFFECTOR_NAME, EFFECTOR_HTTP_COMMAND.getName()) + .configure(HttpCommandEffector.EFFECTOR_URI, url("/get?login=myLogin")) + .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "GET") + .configure(HttpCommandEffector.JSON_PATH, "$.args.login") + ); + assertNotNull(httpCommandEffector); + TestEntity testEntity = app.createAndManageChild(buildEntitySpec(httpCommandEffector)); + Object output = testEntity.invoke(EFFECTOR_HTTP_COMMAND, ImmutableMap.of()).getUnchecked(Duration.seconds(1)); + assertEquals(output, "myLogin"); + + assertEquals(server.getRequestCount(), 1); + assertSent(server, "GET", "/get?login=myLogin"); + } + + private EntitySpec buildEntitySpec(HttpCommandEffector httpCommandEffector) { + return EntitySpec.create(TestEntity.class).addInitializer(httpCommandEffector); + } + +} diff --git a/core/src/test/resources/org/apache/brooklyn/core/effector/http/login.json b/core/src/test/resources/org/apache/brooklyn/core/effector/http/login.json new file mode 100644 index 0000000000..b39889a34f --- /dev/null +++ b/core/src/test/resources/org/apache/brooklyn/core/effector/http/login.json @@ -0,0 +1,16 @@ +{ + "args": { + "login": "myLogin" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch, br", + "Accept-Language": "en-US,en;q=0.8,it;q=0.6", + "Cookie": "_ga=GA1.2.1060288368.1484053495; _gat=1", + "Host": "httpbin.org", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36" + }, + "origin": "93.61.99.89", + "url": "https://httpbin.org/get?login=myLogin" +} From b32581dd81a390d69ff63779465cd83520c7dcab Mon Sep 17 00:00:00 2001 From: Andrea Turli Date: Thu, 2 Feb 2017 17:27:41 +0100 Subject: [PATCH 5/7] add CompositeEffector tests and improve its implementation --- .../brooklyn/CompositeEffectorYamlTest.java | 79 ++++++ .../core/effector/CompositeEffector.java | 68 ++--- .../effector/http/HttpCommandEffector.java | 71 +++-- .../CompositeEffectorIntegrationTest.java | 27 +- .../core/effector/CompositeEffectorTest.java | 262 ++++++++++++++++++ .../http/HttpCommandEffectorTest.java | 88 +++++- .../core/effector/http/int-response.json | 16 ++ .../core/effector/http/list-response.json | 19 ++ .../core/effector/http/map-response.json | 16 ++ .../apache/brooklyn/core/effector/test.json | 16 ++ 10 files changed, 595 insertions(+), 67 deletions(-) create mode 100644 camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/CompositeEffectorYamlTest.java create mode 100644 core/src/test/java/org/apache/brooklyn/core/effector/CompositeEffectorTest.java create mode 100644 core/src/test/resources/org/apache/brooklyn/core/effector/http/int-response.json create mode 100644 core/src/test/resources/org/apache/brooklyn/core/effector/http/list-response.json create mode 100644 core/src/test/resources/org/apache/brooklyn/core/effector/http/map-response.json create mode 100644 core/src/test/resources/org/apache/brooklyn/core/effector/test.json diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/CompositeEffectorYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/CompositeEffectorYamlTest.java new file mode 100644 index 0000000000..f114f238e2 --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/CompositeEffectorYamlTest.java @@ -0,0 +1,79 @@ +/* + * 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 org.apache.brooklyn.camp.brooklyn; + +import static org.testng.Assert.assertEquals; + +import java.util.List; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.core.effector.CompositeEffector; +import org.apache.brooklyn.core.effector.http.HttpCommandEffector; +import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; + +public class CompositeEffectorYamlTest extends AbstractYamlTest { + private static final Logger log = LoggerFactory.getLogger(CompositeEffectorYamlTest.class); + + @Test + public void testCompositeEffector() throws Exception { + Entity app = createAndStartApplication( + "location: localhost", + "services:", + "- type: " + EmptySoftwareProcess.class.getName(), + " brooklyn.config:", + " onbox.base.dir.skipResolution: true", + " softwareProcess.serviceProcessIsRunningPollPeriod: forever", + " brooklyn.initializers:", + " - type: " + HttpCommandEffector.class.getName(), + " brooklyn.config:", + " name: myEffector", + " description: myDescription", + " uri: https://httpbin.org/get?id=myId", + " httpVerb: GET", + " jsonPath: $.args.id", + " publishSensor: results", + " - type: " + CompositeEffector.class.getName(), + " brooklyn.config:", + " name: start", + " override: true", + " effectors:", + " - myEffector" + ); + waitForApplicationTasks(app); + + EmptySoftwareProcess entity = (EmptySoftwareProcess) Iterables.getOnlyElement(app.getChildren()); + Effector effector = entity.getEntityType().getEffectorByName("start").get(); + + // Invoke without parameter + Object results = entity.invoke(effector, ImmutableMap.of()).get(); + assertEquals(((List)results).get(0), "myId"); + } + + @Override + protected Logger getLogger() { + return log; + } +} diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java index 32f0f1dfab..5345b4ae2c 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java @@ -18,11 +18,12 @@ */ package org.apache.brooklyn.core.effector; +import static org.apache.brooklyn.core.entity.trait.Startable.START; +import static org.apache.brooklyn.core.entity.trait.Startable.STOP; + import java.util.List; import java.util.Map; -import javax.annotation.Nullable; - import org.apache.brooklyn.api.effector.Effector; import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.config.ConfigKey; @@ -36,11 +37,8 @@ import org.slf4j.LoggerFactory; import com.google.common.annotations.Beta; -import com.google.common.base.Optional; import com.google.common.base.Preconditions; -import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.reflect.TypeToken; @@ -48,11 +46,14 @@ public class CompositeEffector extends AddEffector { private static final Logger LOG = LoggerFactory.getLogger(CompositeEffector.class); + private static final String ORIGINAL_PREFIX = "original-"; - public static final ConfigKey> EFFECTORS = ConfigKeys.newConfigKey(new TypeToken>() {}, "effectors", + public static final ConfigKey> EFFECTORS = ConfigKeys.newConfigKey(new TypeToken>() { + }, "effectors", "Effector names to be chained together in the composite effector", ImmutableList.of()); public static final ConfigKey OVERRIDE = ConfigKeys.newBooleanConfigKey("override", "Wheter additional defined effectors should override pre-existing effector with same name or not (default: false)", Boolean.FALSE); + public CompositeEffector(ConfigBag params) { super(newEffectorBuilder(params).build()); } @@ -61,8 +62,8 @@ public CompositeEffector(Map params) { this(ConfigBag.newInstance(params)); } - public static EffectorBuilder newEffectorBuilder(ConfigBag params) { - EffectorBuilder eff = AddEffector.newEffectorBuilder(String.class, params); + public static EffectorBuilder newEffectorBuilder(ConfigBag params) { + EffectorBuilder eff = AddEffector.newEffectorBuilder(List.class, params); eff.impl(new Body(eff.buildAbstract(), params)); return eff; } @@ -71,13 +72,13 @@ public static EffectorBuilder newEffectorBuilder(ConfigBag params) { public void apply(EntityLocal entity) { Maybe> effectorMaybe = entity.getEntityType().getEffectorByName(effector.getName()); if (!effectorMaybe.isAbsentOrNull()) { - Effector original = Effectors.effector(effectorMaybe.get()).name("original-" + effector.getName()).build(); - ((EntityInternal)entity).getMutableEntityType().addEffector(original); + Effector original = Effectors.effector(effectorMaybe.get()).name(ORIGINAL_PREFIX + effector.getName()).build(); + ((EntityInternal) entity).getMutableEntityType().addEffector(original); } super.apply(entity); } - protected static class Body extends EffectorBody { + protected static class Body extends EffectorBody { private final Effector effector; private final ConfigBag params; @@ -88,46 +89,47 @@ public Body(Effector eff, ConfigBag params) { } @Override - public String call(final ConfigBag params) { + public List call(final ConfigBag params) { ConfigBag allConfig = ConfigBag.newInstanceCopying(this.params).putAll(params); final List effectorNames = EntityInitializers.resolve(allConfig, EFFECTORS); final Boolean override = allConfig.get(OVERRIDE); List results = Lists.newArrayList(); - if (!override) { - Optional> effectorOptional = Iterables.tryFind(entity().getEntityType().getEffectors(), new Predicate>() { - @Override - public boolean apply(@Nullable Effector input) { - return input.getName().equals("original-" + effector.getName()); - } - }); - // if it is a stop effector, it has to be executed as last effector - if (effectorOptional.isPresent()) { - if (effectorOptional.get().getName().endsWith("-stop")) { - effectorNames.add(effectorOptional.get().getName()); - } else { - effectorNames.add(0, effectorOptional.get().getName()); - } - } - } + if (!override && isStartRedefined()) { + results.add(invokeEffectorNamed(ORIGINAL_PREFIX + START.getName(), params)); + } for (String eff : effectorNames) { results.add(invokeEffectorNamed(eff, params)); } - return Iterables.toString(results); + if (!override && isStopRedefined()) { + results.add(invokeEffectorNamed(ORIGINAL_PREFIX + STOP.getName(), params)); + } + return results; + } + + private boolean isStartRedefined() { + return isEffectorRedefined(ORIGINAL_PREFIX + START.getName()); + } + + private boolean isStopRedefined() { + return isEffectorRedefined(ORIGINAL_PREFIX + STOP.getName()); + } + + private boolean isEffectorRedefined(String effectorName) { + return entity().getEntityType().getEffectorByName(effectorName).isPresent(); } private Object invokeEffectorNamed(String effectorName, ConfigBag params) { - LOG.debug("{} invoking effector on {}, effector={}, parameters={}", + LOG.info("{} invoking effector on {}, effector={}, parameters={}", new Object[]{this, entity(), effectorName, params}); Maybe> effector = entity().getEntityType().getEffectorByName(effectorName); if (effector.isAbsent()) { - // TODO + throw new IllegalStateException("Cannot find effector " + effectorName); } return entity().invoke(effector.get(), params.getAllConfig()).getUnchecked(); - } - } + } } diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/http/HttpCommandEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/http/HttpCommandEffector.java index 2d8266e581..1388a926d6 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/http/HttpCommandEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/http/HttpCommandEffector.java @@ -19,6 +19,7 @@ package org.apache.brooklyn.core.effector.http; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -49,28 +50,33 @@ import org.apache.brooklyn.util.http.executor.HttpResponse; import org.apache.brooklyn.util.http.executor.UsernamePassword; import org.apache.brooklyn.util.http.executor.apacheclient.HttpExecutorImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.google.common.base.Enums; import com.google.common.base.Joiner; import com.google.common.base.Optional; -import com.google.common.base.Preconditions; import com.google.common.io.ByteStreams; import com.google.common.net.HttpHeaders; import com.jayway.jsonpath.JsonPath; public final class HttpCommandEffector extends AddEffector { + private static final Logger LOG = LoggerFactory.getLogger(HttpCommandEffector.class); + public static final ConfigKey EFFECTOR_URI = ConfigKeys.newStringConfigKey("uri"); public static final ConfigKey EFFECTOR_HTTP_VERB = ConfigKeys.newStringConfigKey("httpVerb"); public static final ConfigKey EFFECTOR_HTTP_USERNAME = ConfigKeys.newStringConfigKey("httpUsername"); public static final ConfigKey EFFECTOR_HTTP_PASSWORD = ConfigKeys.newStringConfigKey("httpPassword"); public static final ConfigKey> EFFECTOR_HTTP_HEADERS = new MapConfigKey(String.class, "headers"); - public static final ConfigKey> EFFECTOR_HTTP_PAYLOAD = new MapConfigKey(String.class, "httpPayload"); + public static final ConfigKey EFFECTOR_HTTP_PAYLOAD = ConfigKeys.newConfigKey(Object.class, "httpPayload"); public static final ConfigKey JSON_PATH = ConfigKeys.newStringConfigKey("jsonPath", "JSON path to select in HTTP response"); public static final ConfigKey PUBLISH_SENSOR = ConfigKeys.newStringConfigKey("publishSensor", "Sensor name where to store json path extracted value"); + public static final String APPLICATION_JSON = "application/json"; + private enum HttpVerb { - GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE; + GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE } public HttpCommandEffector(ConfigBag params) { @@ -89,29 +95,28 @@ protected static class Body extends EffectorBody { public Body(Effector eff, final ConfigBag params) { this.effector = eff; - Preconditions.checkNotNull(params.getAllConfigRaw().get(EFFECTOR_URI.getName()), "uri must be supplied when defining this effector"); - Preconditions.checkNotNull(params.getAllConfigRaw().get(EFFECTOR_HTTP_VERB.getName()), "HTTP verb must be supplied when defining this effector"); + checkNotNull(params.getAllConfigRaw().get(EFFECTOR_URI.getName()), "uri must be supplied when defining this effector"); + checkNotNull(params.getAllConfigRaw().get(EFFECTOR_HTTP_VERB.getName()), "HTTP verb must be supplied when defining this effector"); this.params = params; } @Override public String call(final ConfigBag params) { ConfigBag allConfig = ConfigBag.newInstanceCopying(this.params).putAll(params); - final String uri = EntityInitializers.resolve(allConfig, EFFECTOR_URI); - final String httpVerb = EntityInitializers.resolve(allConfig, EFFECTOR_HTTP_VERB); + final URI uri = convertToURI(EntityInitializers.resolve(allConfig, EFFECTOR_URI)); + final String httpVerb = isValidHttpVerb(EntityInitializers.resolve(allConfig, EFFECTOR_HTTP_VERB)); final String httpUsername = EntityInitializers.resolve(allConfig, EFFECTOR_HTTP_USERNAME); final String httpPassword = EntityInitializers.resolve(allConfig, EFFECTOR_HTTP_PASSWORD); final Map headers = EntityInitializers.resolve(allConfig, EFFECTOR_HTTP_HEADERS); - final Map payload = EntityInitializers.resolve(allConfig, EFFECTOR_HTTP_PAYLOAD); + final Object payload = EntityInitializers.resolve(allConfig, EFFECTOR_HTTP_PAYLOAD); final String jsonPath = EntityInitializers.resolve(allConfig, JSON_PATH); final String publishSensor = EntityInitializers.resolve(allConfig, PUBLISH_SENSOR); + final HttpExecutor httpExecutor = HttpExecutorImpl.newInstance(); + + final HttpRequest request = buildHttpRequest(httpVerb, uri, headers, httpUsername, httpPassword, payload); Task t = Tasks.builder().displayName(effector.getName()).body(new Callable() { @Override public Object call() throws Exception { - HttpExecutor httpExecutor = HttpExecutorImpl.newInstance(); - - String body = getBodyFromPayload(payload, headers); - HttpRequest request = buildHttpRequest(httpVerb, uri, headers, httpUsername, httpPassword, body); ByteArrayOutputStream out = new ByteArrayOutputStream(); try { HttpResponse response = httpExecutor.execute(request); @@ -127,6 +132,7 @@ public Object call() throws Exception { String responseBody = (String) queue(t).getUnchecked(); if (jsonPath == null) return responseBody; + String extractedValue = JsonPath.parse(responseBody).read(jsonPath, String.class); if (publishSensor != null) { entity().sensors().set(Sensors.newStringSensor(publishSensor), extractedValue); @@ -134,6 +140,16 @@ public Object call() throws Exception { return extractedValue; } + private URI convertToURI(String url) { + try { + return new URL(url).toURI(); + } catch (MalformedURLException e) { + throw Exceptions.propagate(e); + } catch (URISyntaxException e) { + throw Exceptions.propagate(e); + } + } + private void validateResponse(HttpResponse response) { int statusCode = response.code(); if (statusCode == 401) { @@ -145,13 +161,8 @@ private void validateResponse(HttpResponse response) { } } - private HttpRequest buildHttpRequest(String httpVerb, String url, Map headers, String httpUsername, String httpPassword, String body) throws MalformedURLException, URISyntaxException { - // validate url string - URI uri = new URL(url).toURI(); - // validate HTTP verb - validateHttpVerb(httpVerb); + private HttpRequest buildHttpRequest(String httpVerb, URI uri, Map headers, String httpUsername, String httpPassword, Object payload) { HttpRequest.Builder httpRequestBuilder = new HttpRequest.Builder() - .body(body.getBytes()) .uri(uri) .method(httpVerb) .config(HttpConfig.builder() @@ -164,6 +175,19 @@ private HttpRequest buildHttpRequest(String httpVerb, String url, Map state = Enums.getIfPresent(HttpVerb.class, httpVerb.toUpperCase()); checkArgument(state.isPresent(), "Expected one of %s but was %s", Joiner.on(',').join(HttpVerb.values()), httpVerb); + return httpVerb; } - private String getBodyFromPayload(Map payload, Map headers) { - String body = ""; - if (payload != null && !payload.isEmpty() && headers.containsKey(HttpHeaders.CONTENT_TYPE)) { - body = Jsonya.newInstance().put(payload).toString(); - } - return body; + private String toJsonString(Object payload) { + return Jsonya.newInstance().add(payload).toString(); } } diff --git a/core/src/test/java/org/apache/brooklyn/core/effector/CompositeEffectorIntegrationTest.java b/core/src/test/java/org/apache/brooklyn/core/effector/CompositeEffectorIntegrationTest.java index e8cc30e04d..dd168ed5ac 100644 --- a/core/src/test/java/org/apache/brooklyn/core/effector/CompositeEffectorIntegrationTest.java +++ b/core/src/test/java/org/apache/brooklyn/core/effector/CompositeEffectorIntegrationTest.java @@ -18,6 +18,12 @@ */ package org.apache.brooklyn.core.effector; +import static org.apache.brooklyn.test.Asserts.assertEquals; +import static org.apache.brooklyn.test.Asserts.assertNull; +import static org.apache.brooklyn.test.Asserts.assertTrue; + +import java.util.List; + import org.apache.brooklyn.api.effector.Effector; import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.api.entity.EntitySpec; @@ -36,11 +42,11 @@ public class CompositeEffectorIntegrationTest { - final static Effector EFFECTOR_START = Effectors.effector(String.class, "start").buildAbstract(); + final static Effector EFFECTOR_START = Effectors.effector(List.class, "start").buildAbstract(); private TestApplication app; private EntityLocal entity; - + @BeforeMethod(alwaysRun=true) public void setUp() throws Exception { app = TestApplication.Factory.newManagedInstanceForTests(); @@ -58,21 +64,28 @@ public void testCompositeEffector() throws Exception { new HttpCommandEffector(ConfigBag.newInstance() .configure(HttpCommandEffector.EFFECTOR_NAME, "eff1") .configure(HttpCommandEffector.EFFECTOR_URI, "https://api.github.com/users/apache") - .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "GET")) + .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "GET") + .configure(HttpCommandEffector.JSON_PATH, "$.login")) .apply(entity); new HttpCommandEffector(ConfigBag.newInstance() .configure(HttpCommandEffector.EFFECTOR_NAME, "eff2") .configure(HttpCommandEffector.EFFECTOR_URI, "https://api.github.com/users/brooklyncentral") - .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "GET")) + .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "GET") + .configure(HttpCommandEffector.JSON_PATH, "$.login")) .apply(entity); new CompositeEffector(ConfigBag.newInstance() .configure(CompositeEffector.EFFECTOR_NAME, "start") .configure(CompositeEffector.EFFECTORS, ImmutableList.of("eff1", "eff2"))) .apply(entity); - String val = entity.invoke(EFFECTOR_START, MutableMap.of()).get(); - // TODO - System.out.println(val); + List results = entity.invoke(EFFECTOR_START, MutableMap.of()).get(); + + assertEquals(results.size(), 3); + assertNull(results.get(0)); + assertTrue(results.get(1) instanceof String); + assertEquals(results.get(1), "apache"); + assertTrue(results.get(2) instanceof String); + assertEquals(results.get(2), "brooklyncentral"); } } diff --git a/core/src/test/java/org/apache/brooklyn/core/effector/CompositeEffectorTest.java b/core/src/test/java/org/apache/brooklyn/core/effector/CompositeEffectorTest.java new file mode 100644 index 0000000000..3bf0018e5f --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/core/effector/CompositeEffectorTest.java @@ -0,0 +1,262 @@ +/* + * 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 org.apache.brooklyn.core.effector; + +import static org.apache.brooklyn.core.effector.http.HttpCommandEffectorTest.EFFECTOR_HTTP_COMMAND; +import static org.apache.brooklyn.test.Asserts.assertNotNull; +import static org.apache.brooklyn.test.Asserts.assertNull; +import static org.apache.brooklyn.test.Asserts.assertTrue; + +import java.io.IOException; +import java.net.URL; +import java.util.List; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.core.effector.http.HttpCommandEffector; +import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; +import org.apache.brooklyn.core.test.entity.TestEntity; +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.core.http.BetterMockWebServer; +import org.apache.brooklyn.util.exceptions.PropagatedRuntimeException; +import org.apache.brooklyn.util.time.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.base.Charsets; +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.io.Resources; +import com.google.mockwebserver.MockResponse; + +public class CompositeEffectorTest extends BrooklynAppUnitTestSupport { + + private static final Logger log = LoggerFactory.getLogger(CompositeEffectorTest.class); + private static final String DEFAULT_ENDPOINT = "/"; + + final static Effector EFFECTOR_COMPOSITE = Effectors.effector(List.class, "CompositeEffector").buildAbstract(); + + protected BetterMockWebServer server; + protected URL baseUrl; + + protected Location loc; + protected CompositeEffector compositeEffector; + + @BeforeMethod + public void start() throws IOException { + server = BetterMockWebServer.newInstanceLocalhost(); + server.play(); + } + + @AfterMethod(alwaysRun = true) + public void stop() throws IOException { + server.shutdown(); + } + + protected String url(String path) { + return server.getUrl(path).toString(); + } + + protected MockResponse jsonResponse(String resource) { + return new MockResponse().addHeader("Content-Type", "application/json").setBody(stringFromResource(resource)); + } + + protected MockResponse response404() { + return new MockResponse().setStatus("HTTP/1.1 404 Not Found"); + } + + protected MockResponse response204() { + return new MockResponse().setStatus("HTTP/1.1 204 No Content"); + } + + protected String stringFromResource(String resourceName) { + return stringFromResource("/org/apache/brooklyn/core/effector", resourceName); + } + + private String stringFromResource(String prefix, String resourceName) { + try { + return Resources.toString(getClass().getResource(String.format("%s/%s", prefix, resourceName)), Charsets.UTF_8) + .replace(DEFAULT_ENDPOINT, url("")); + } catch (IOException e) { + throw Throwables.propagate(e); + } + } + + @Test + public void testCompositeEffectorWithNonExistingName() throws InterruptedException { + server.enqueue(jsonResponse("test.json")); + + HttpCommandEffector httpCommandEffector = new HttpCommandEffector(ConfigBag.newInstance() + .configure(HttpCommandEffector.EFFECTOR_NAME, EFFECTOR_HTTP_COMMAND.getName()) + .configure(HttpCommandEffector.EFFECTOR_URI, url("/get?login=myLogin")) + .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "GET") + .configure(HttpCommandEffector.JSON_PATH, "$.args.login") + ); + assertNotNull(httpCommandEffector); + compositeEffector = new CompositeEffector(ConfigBag.newInstance() + .configure(CompositeEffector.EFFECTOR_NAME, EFFECTOR_COMPOSITE.getName()) + .configure(CompositeEffector.EFFECTORS, ImmutableList.of(EFFECTOR_HTTP_COMMAND.getName())) + ); + assertNotNull(compositeEffector); + TestEntity testEntity = app.createAndManageChild(buildEntitySpec(httpCommandEffector, compositeEffector)); + List results = testEntity.invoke(EFFECTOR_COMPOSITE, ImmutableMap.of()).getUnchecked(Duration.seconds(1)); + Asserts.assertEquals(results.size(), 1); + + assertTrue(results.get(0) instanceof String); + Asserts.assertEquals(results.get(0), "myLogin"); + } + + @Test + public void testCompositeEffectorWithStartName() throws InterruptedException { + server.enqueue(jsonResponse("test.json")); + + HttpCommandEffector httpCommandEffector = new HttpCommandEffector(ConfigBag.newInstance() + .configure(HttpCommandEffector.EFFECTOR_NAME, EFFECTOR_HTTP_COMMAND.getName()) + .configure(HttpCommandEffector.EFFECTOR_URI, url("/get?login=myLogin")) + .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "GET") + .configure(HttpCommandEffector.JSON_PATH, "$.args.login") + ); + assertNotNull(httpCommandEffector); + compositeEffector = new CompositeEffector(ConfigBag.newInstance() + .configure(CompositeEffector.EFFECTOR_NAME, "start") + .configure(CompositeEffector.EFFECTORS, ImmutableList.of(EFFECTOR_HTTP_COMMAND.getName())) + ); + assertNotNull(compositeEffector); + TestEntity testEntity = app.createAndManageChild(buildEntitySpec(httpCommandEffector, compositeEffector)); + List results = testEntity.invoke(Effectors.effector(List.class, "start").buildAbstract(), ImmutableMap.of()).getUnchecked(Duration.seconds(1)); + Asserts.assertEquals(results.size(), 2); + assertNull(results.get(0)); + assertTrue(results.get(1) instanceof String); + Asserts.assertEquals(results.get(1), "myLogin"); + } + + @Test + public void testCompositeEffectorWithStartNameAndOverriding() throws InterruptedException { + server.enqueue(jsonResponse("test.json")); + + HttpCommandEffector httpCommandEffector = new HttpCommandEffector(ConfigBag.newInstance() + .configure(HttpCommandEffector.EFFECTOR_NAME, EFFECTOR_HTTP_COMMAND.getName()) + .configure(HttpCommandEffector.EFFECTOR_URI, url("/get?login=myLogin")) + .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "GET") + .configure(HttpCommandEffector.JSON_PATH, "$.args.login") + ); + assertNotNull(httpCommandEffector); + compositeEffector = new CompositeEffector(ConfigBag.newInstance() + .configure(CompositeEffector.EFFECTOR_NAME, "start") + .configure(CompositeEffector.OVERRIDE, true) + .configure(CompositeEffector.EFFECTORS, ImmutableList.of(EFFECTOR_HTTP_COMMAND.getName())) + ); + assertNotNull(compositeEffector); + TestEntity testEntity = app.createAndManageChild(buildEntitySpec(httpCommandEffector, compositeEffector)); + List results = testEntity.invoke(Effectors.effector(List.class, "start").buildAbstract(), ImmutableMap.of()).getUnchecked(Duration.seconds(1)); + Asserts.assertEquals(results.size(), 1); + assertTrue(results.get(0) instanceof String); + Asserts.assertEquals(results.get(0), "myLogin"); + } + + @Test + public void testCompositeEffectorWithStopName() throws InterruptedException { + server.enqueue(jsonResponse("test.json")); + + HttpCommandEffector httpCommandEffector = new HttpCommandEffector(ConfigBag.newInstance() + .configure(HttpCommandEffector.EFFECTOR_NAME, EFFECTOR_HTTP_COMMAND.getName()) + .configure(HttpCommandEffector.EFFECTOR_URI, url("/get?login=myLogin")) + .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "GET") + .configure(HttpCommandEffector.JSON_PATH, "$.args.login") + ); + assertNotNull(httpCommandEffector); + compositeEffector = new CompositeEffector(ConfigBag.newInstance() + .configure(CompositeEffector.EFFECTOR_NAME, "stop") + .configure(CompositeEffector.EFFECTORS, ImmutableList.of(EFFECTOR_HTTP_COMMAND.getName())) + ); + assertNotNull(compositeEffector); + TestEntity testEntity = app.createAndManageChild(buildEntitySpec(httpCommandEffector, compositeEffector)); + List results = testEntity.invoke(Effectors.effector(List.class, "stop").buildAbstract(), ImmutableMap.of()).getUnchecked(Duration.minutes(1)); + Asserts.assertEquals(results.size(), 2); + assertTrue(results.get(0) instanceof String); + Asserts.assertEquals(results.get(0), "myLogin"); + assertNull(results.get(1)); + } + + @Test + public void testCompositeEffectorWithStopNameAndOverriding() throws InterruptedException { + server.enqueue(jsonResponse("test.json")); + + HttpCommandEffector httpCommandEffector = new HttpCommandEffector(ConfigBag.newInstance() + .configure(HttpCommandEffector.EFFECTOR_NAME, EFFECTOR_HTTP_COMMAND.getName()) + .configure(HttpCommandEffector.EFFECTOR_URI, url("/get?login=myLogin")) + .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "GET") + .configure(HttpCommandEffector.JSON_PATH, "$.args.login") + ); + assertNotNull(httpCommandEffector); + compositeEffector = new CompositeEffector(ConfigBag.newInstance() + .configure(CompositeEffector.EFFECTOR_NAME, "stop") + .configure(CompositeEffector.OVERRIDE, true) + .configure(CompositeEffector.EFFECTORS, ImmutableList.of(EFFECTOR_HTTP_COMMAND.getName())) + ); + assertNotNull(compositeEffector); + TestEntity testEntity = app.createAndManageChild(buildEntitySpec(httpCommandEffector, compositeEffector)); + List results = testEntity.invoke(Effectors.effector(List.class, "stop").buildAbstract(), ImmutableMap.of()).getUnchecked(Duration.minutes(1)); + Asserts.assertEquals(results.size(), 1); + assertTrue(results.get(0) instanceof String); + Asserts.assertEquals(results.get(0), "myLogin"); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testMissingEffectors() { + compositeEffector = new CompositeEffector(ConfigBag.newInstance() + .configure(CompositeEffector.EFFECTOR_NAME, EFFECTOR_COMPOSITE.getName()) + .configure(CompositeEffector.EFFECTORS, null) + ); + } + + @Test(expectedExceptions = PropagatedRuntimeException.class) + public void testWhenOneEffectorFails() throws InterruptedException { + server.enqueue(response404()); + + HttpCommandEffector eff1 = new HttpCommandEffector(ConfigBag.newInstance() + .configure(HttpCommandEffector.EFFECTOR_NAME, "eff1") + .configure(HttpCommandEffector.EFFECTOR_URI, url("/get?login=myLogin")) + .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "GET") + .configure(HttpCommandEffector.JSON_PATH, "$.args.login")); + compositeEffector = new CompositeEffector(ConfigBag.newInstance() + .configure(CompositeEffector.EFFECTOR_NAME, EFFECTOR_COMPOSITE.getName()) + .configure(CompositeEffector.EFFECTORS, ImmutableList.of("eff1", "eff2")) + ); + assertNotNull(compositeEffector); + TestEntity testEntity = app.createAndManageChild(buildEntitySpec(eff1, compositeEffector)); + List results = testEntity.invoke(EFFECTOR_COMPOSITE, ImmutableMap.of()).getUnchecked(Duration.seconds(1)); + Asserts.assertEquals(results.size(), 2); + } + + private EntitySpec buildEntitySpec(AddEffector... effectors) { + EntitySpec testEntitySpec = EntitySpec.create(TestEntity.class); + for (AddEffector effector : effectors) { + testEntitySpec.addInitializer(effector); + } + return testEntitySpec; + } + +} diff --git a/core/src/test/java/org/apache/brooklyn/core/effector/http/HttpCommandEffectorTest.java b/core/src/test/java/org/apache/brooklyn/core/effector/http/HttpCommandEffectorTest.java index a4b8ee04cc..0580c39ea3 100644 --- a/core/src/test/java/org/apache/brooklyn/core/effector/http/HttpCommandEffectorTest.java +++ b/core/src/test/java/org/apache/brooklyn/core/effector/http/HttpCommandEffectorTest.java @@ -44,8 +44,10 @@ import com.google.common.base.Charsets; import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.io.Resources; +import com.google.common.net.HttpHeaders; import com.google.mockwebserver.MockResponse; import com.google.mockwebserver.RecordedRequest; @@ -54,7 +56,7 @@ public class HttpCommandEffectorTest extends BrooklynAppUnitTestSupport { private static final Logger log = LoggerFactory.getLogger(HttpCommandEffectorTest.class); private static final String DEFAULT_ENDPOINT = "/"; - final static Effector EFFECTOR_HTTP_COMMAND = Effectors.effector(String.class, "http-command-effector").buildAbstract(); + public final static Effector EFFECTOR_HTTP_COMMAND = Effectors.effector(String.class, "http-command-effector").buildAbstract(); protected BetterMockWebServer server; protected URL baseUrl; @@ -78,7 +80,7 @@ protected String url(String path) { } protected MockResponse jsonResponse(String resource) { - return new MockResponse().addHeader("Content-Type", "application/json").setBody(stringFromResource(resource)); + return new MockResponse().addHeader(HttpHeaders.CONTENT_TYPE, "application/json").setBody(stringFromResource(resource)); } protected MockResponse response404() { @@ -143,6 +145,88 @@ public void testInvalidVerb() throws ExecutionException, InterruptedException { testEntity.invoke(EFFECTOR_HTTP_COMMAND, MutableMap.of()).get(); } + @Test + public void testPayloadWithContentTypeHeaderJson() throws InterruptedException { + server.enqueue(jsonResponse("map-response.json")); + + httpCommandEffector = new HttpCommandEffector(ConfigBag.newInstance() + .configure(HttpCommandEffector.EFFECTOR_NAME, EFFECTOR_HTTP_COMMAND.getName()) + .configure(HttpCommandEffector.EFFECTOR_URI, url("/post")) + .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "POST") + .configure(HttpCommandEffector.EFFECTOR_HTTP_PAYLOAD, ImmutableMap.of("key", "value")) + .configure(HttpCommandEffector.EFFECTOR_HTTP_HEADERS, ImmutableMap.of(HttpHeaders.CONTENT_TYPE, "application/json")) + .configure(HttpCommandEffector.JSON_PATH, "$.data") + ); + assertNotNull(httpCommandEffector); + TestEntity testEntity = app.createAndManageChild(buildEntitySpec(httpCommandEffector)); + Object output = testEntity.invoke(EFFECTOR_HTTP_COMMAND, ImmutableMap.of()).getUnchecked(Duration.minutes(1)); + assertEquals(output, "{\"key\", \"value\"}"); + + assertEquals(server.getRequestCount(), 1); + assertSent(server, "POST", "/post"); + } + + @Test + public void testPayloadWithoutContentTypeHeader() throws InterruptedException { + server.enqueue(jsonResponse("map-response.json")); + + httpCommandEffector = new HttpCommandEffector(ConfigBag.newInstance() + .configure(HttpCommandEffector.EFFECTOR_NAME, EFFECTOR_HTTP_COMMAND.getName()) + .configure(HttpCommandEffector.EFFECTOR_URI, url("/post")) + .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "POST") + .configure(HttpCommandEffector.EFFECTOR_HTTP_PAYLOAD, ImmutableMap.of("key", "value")) + .configure(HttpCommandEffector.JSON_PATH, "$.data") + ); + assertNotNull(httpCommandEffector); + TestEntity testEntity = app.createAndManageChild(buildEntitySpec(httpCommandEffector)); + Object output = testEntity.invoke(EFFECTOR_HTTP_COMMAND, ImmutableMap.of()).getUnchecked(Duration.seconds(1)); + assertEquals(output, "{\"key\", \"value\"}"); + + assertEquals(server.getRequestCount(), 1); + assertSent(server, "POST", "/post"); + } + + @Test + public void testListPayloadWithoutContentTypeHeader() throws InterruptedException { + server.enqueue(jsonResponse("list-response.json")); + + httpCommandEffector = new HttpCommandEffector(ConfigBag.newInstance() + .configure(HttpCommandEffector.EFFECTOR_NAME, EFFECTOR_HTTP_COMMAND.getName()) + .configure(HttpCommandEffector.EFFECTOR_URI, url("/post")) + .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "POST") + .configure(HttpCommandEffector.EFFECTOR_HTTP_PAYLOAD, ImmutableList.of("key", "value")) + .configure(HttpCommandEffector.JSON_PATH, "$.data") + ); + assertNotNull(httpCommandEffector); + TestEntity testEntity = app.createAndManageChild(buildEntitySpec(httpCommandEffector)); + Object output = testEntity.invoke(EFFECTOR_HTTP_COMMAND, ImmutableMap.of()).getUnchecked(Duration.seconds(1)); + assertEquals(output, "[\"key\", \"value\"]"); + + assertEquals(server.getRequestCount(), "[\"key\", \"value\"]"); + assertSent(server, "POST", "/post"); + } + + @Test + public void testPayloadWithContentTypeHeaderXml() throws InterruptedException { + server.enqueue(jsonResponse("int-response.json")); + + httpCommandEffector = new HttpCommandEffector(ConfigBag.newInstance() + .configure(HttpCommandEffector.EFFECTOR_NAME, EFFECTOR_HTTP_COMMAND.getName()) + .configure(HttpCommandEffector.EFFECTOR_URI, url("/post")) + .configure(HttpCommandEffector.EFFECTOR_HTTP_VERB, "POST") + .configure(HttpCommandEffector.EFFECTOR_HTTP_PAYLOAD, 1) + .configure(HttpCommandEffector.EFFECTOR_HTTP_HEADERS, ImmutableMap.of(HttpHeaders.CONTENT_TYPE, "application/xml")) + .configure(HttpCommandEffector.JSON_PATH, "$.data") + ); + assertNotNull(httpCommandEffector); + TestEntity testEntity = app.createAndManageChild(buildEntitySpec(httpCommandEffector)); + Object output = testEntity.invoke(EFFECTOR_HTTP_COMMAND, ImmutableMap.of()).getUnchecked(Duration.seconds(1)); + assertEquals(output, "1"); + + assertEquals(server.getRequestCount(), 1); + assertSent(server, "POST", "/post"); + } + @Test public void testHappyPath() throws InterruptedException { server.enqueue(jsonResponse("login.json")); diff --git a/core/src/test/resources/org/apache/brooklyn/core/effector/http/int-response.json b/core/src/test/resources/org/apache/brooklyn/core/effector/http/int-response.json new file mode 100644 index 0000000000..9b133f74f4 --- /dev/null +++ b/core/src/test/resources/org/apache/brooklyn/core/effector/http/int-response.json @@ -0,0 +1,16 @@ +{ + "args": {}, + "data": "1", + "files": {}, + "form": {}, + "headers": { + "Accept": "*/*", + "Content-Length": "1", + "Content-Type": "application/json", + "Host": "httpbin.org", + "User-Agent": "curl/7.49.1" + }, + "json": 1, + "origin": "93.61.99.89", + "url": "http://httpbin.org/post" +} diff --git a/core/src/test/resources/org/apache/brooklyn/core/effector/http/list-response.json b/core/src/test/resources/org/apache/brooklyn/core/effector/http/list-response.json new file mode 100644 index 0000000000..88394ea389 --- /dev/null +++ b/core/src/test/resources/org/apache/brooklyn/core/effector/http/list-response.json @@ -0,0 +1,19 @@ +{ + "args": {}, + "data": "[\"key\", \"value\"]", + "files": {}, + "form": {}, + "headers": { + "Accept": "*/*", + "Content-Length": "16", + "Content-Type": "application/json", + "Host": "httpbin.org", + "User-Agent": "curl/7.49.1" + }, + "json": [ + "key", + "value" + ], + "origin": "93.61.99.89", + "url": "http://httpbin.org/post" +} diff --git a/core/src/test/resources/org/apache/brooklyn/core/effector/http/map-response.json b/core/src/test/resources/org/apache/brooklyn/core/effector/http/map-response.json new file mode 100644 index 0000000000..959b763ec1 --- /dev/null +++ b/core/src/test/resources/org/apache/brooklyn/core/effector/http/map-response.json @@ -0,0 +1,16 @@ +{ + "args": {}, + "data": "{\"key\", \"value\"}", + "files": {}, + "form": {}, + "headers": { + "Accept": "*/*", + "Content-Length": "16", + "Content-Type": "application/json", + "Host": "httpbin.org", + "User-Agent": "curl/7.49.1" + }, + "json": null, + "origin": "93.61.99.89", + "url": "http://httpbin.org/post" +} diff --git a/core/src/test/resources/org/apache/brooklyn/core/effector/test.json b/core/src/test/resources/org/apache/brooklyn/core/effector/test.json new file mode 100644 index 0000000000..b39889a34f --- /dev/null +++ b/core/src/test/resources/org/apache/brooklyn/core/effector/test.json @@ -0,0 +1,16 @@ +{ + "args": { + "login": "myLogin" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch, br", + "Accept-Language": "en-US,en;q=0.8,it;q=0.6", + "Cookie": "_ga=GA1.2.1060288368.1484053495; _gat=1", + "Host": "httpbin.org", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36" + }, + "origin": "93.61.99.89", + "url": "https://httpbin.org/get?login=myLogin" +} From 8809d5cdde60f65636fafdce2917ea37e550f1e1 Mon Sep 17 00:00:00 2001 From: Andrea Turli Date: Thu, 2 Feb 2017 17:34:09 +0100 Subject: [PATCH 6/7] add CompositeEffectorYamlRebindTest --- .../CompositeEffectorYamlRebindTest.java | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/CompositeEffectorYamlRebindTest.java diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/CompositeEffectorYamlRebindTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/CompositeEffectorYamlRebindTest.java new file mode 100644 index 0000000000..09a9075efe --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/CompositeEffectorYamlRebindTest.java @@ -0,0 +1,102 @@ +/* + * 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 org.apache.brooklyn.camp.brooklyn; + +import static org.apache.brooklyn.test.Asserts.assertFalse; +import static org.testng.Assert.assertEquals; + +import java.util.List; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.core.effector.CompositeEffector; +import org.apache.brooklyn.core.effector.http.HttpCommandEffector; +import org.apache.brooklyn.core.entity.EntityPredicates; +import org.apache.brooklyn.core.entity.StartableApplication; +import org.apache.brooklyn.core.test.entity.TestEntity; +import org.apache.brooklyn.util.guava.Maybe; +import org.testng.annotations.Test; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; + +@Test +public class CompositeEffectorYamlRebindTest extends AbstractYamlRebindTest { + + private final static String appId = "my-app-with-composite-effector"; + private final static String appVersion = "1.0.0-SNAPSHOT"; + static final String appVersionedId = appId + ":" + appVersion; + + static final String catalogYamlSimple = Joiner.on("\n").join( + "brooklyn.catalog:", + " id: " + appId, + " version: " + appVersion, + " itemType: entity", + " item:", + " type: " + TestEntity.class.getName(), + " name: targetEntity", + " brooklyn.initializers:", + " - type: " + HttpCommandEffector.class.getName(), + " brooklyn.config:", + " name: myEffector", + " description: myDescription", + " uri: https://httpbin.org/get?id=myId", + " httpVerb: GET", + " jsonPath: $.args.id", + " publishSensor: results", + " - type: " + CompositeEffector.class.getName(), + " brooklyn.config:", + " name: start", + " override: true", + " effectors:", + " - myEffector" + ); + + @Test + public void testRebindWhenHealthy() throws Exception { + runRebindWhenIsUp(catalogYamlSimple, appVersionedId); + } + + protected void runRebindWhenIsUp(String catalogYaml, String appId) throws Exception { + addCatalogItems(catalogYaml); + + String appYaml = Joiner.on("\n").join( + "services: ", + "- type: " + appId); + createStartWaitAndLogApplication(appYaml); + + // Rebind + StartableApplication newApp = rebind(); + TestEntity testEntity = (TestEntity) Iterables.find(newApp.getChildren(), EntityPredicates.displayNameEqualTo("targetEntity")); + Effector effector = assertHasInitializers(testEntity, "start"); + + // Confirm HttpCommandEffector still functions + Object results = testEntity.invoke(effector, ImmutableMap.of()).get(); + assertEquals(((List)results).get(0), "myId"); + } + + + protected static Effector assertHasInitializers(Entity entity, String effectorName) { + Maybe> effectorMaybe = entity.getEntityType().getEffectorByName(effectorName); + assertFalse(effectorMaybe.isAbsent()); + return effectorMaybe.get(); + } + +} From 1871a5219f2a25e172e2b5367315aef55bb79457 Mon Sep 17 00:00:00 2001 From: Andrea Turli Date: Mon, 13 Feb 2017 15:39:39 +0100 Subject: [PATCH 7/7] fix merge errors --- .../camp/brooklyn/AbstractYamlRebindTest.java | 11 ----------- .../core/effector/http/HttpCommandEffectorTest.java | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlRebindTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlRebindTest.java index 9168b25f58..0d6d5c259b 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlRebindTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlRebindTest.java @@ -29,14 +29,7 @@ import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.mgmt.Task; -<<<<<<< 3d318e4e78085519233bcc763e22320ca40403df -import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatform; -import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherNoServer; import org.apache.brooklyn.camp.brooklyn.spi.creation.CampTypePlanTransformer; -======= ->>>>>>> add more tests for HttpCommandEffector -import org.apache.brooklyn.camp.spi.Assembly; -import org.apache.brooklyn.camp.spi.AssemblyTemplate; import org.apache.brooklyn.core.catalog.internal.CatalogUtils; import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.entity.StartableApplication; @@ -48,7 +41,6 @@ import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.core.ResourceUtils; -import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.stream.Streams; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,9 +48,6 @@ import org.testng.annotations.BeforeMethod; import com.google.common.base.Joiner; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Iterables; public class AbstractYamlRebindTest extends RebindTestFixture { diff --git a/core/src/test/java/org/apache/brooklyn/core/effector/http/HttpCommandEffectorTest.java b/core/src/test/java/org/apache/brooklyn/core/effector/http/HttpCommandEffectorTest.java index 0580c39ea3..8d620a4881 100644 --- a/core/src/test/java/org/apache/brooklyn/core/effector/http/HttpCommandEffectorTest.java +++ b/core/src/test/java/org/apache/brooklyn/core/effector/http/HttpCommandEffectorTest.java @@ -202,7 +202,7 @@ public void testListPayloadWithoutContentTypeHeader() throws InterruptedExceptio Object output = testEntity.invoke(EFFECTOR_HTTP_COMMAND, ImmutableMap.of()).getUnchecked(Duration.seconds(1)); assertEquals(output, "[\"key\", \"value\"]"); - assertEquals(server.getRequestCount(), "[\"key\", \"value\"]"); + assertEquals(server.getRequestCount(), 1); assertSent(server, "POST", "/post"); }