blob: e9abdcd9e74ba45de33739ad4bcf5a057c0bd157 [file] [log] [blame]
/*
* 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.workflow;
import com.google.mockwebserver.MockResponse;
import org.apache.brooklyn.api.entity.EntityLocal;
import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.api.location.LocationSpec;
import org.apache.brooklyn.api.location.NoMachinesAvailableException;
import org.apache.brooklyn.api.mgmt.Task;
import org.apache.brooklyn.core.entity.EntityAsserts;
import org.apache.brooklyn.core.entity.EntityInternal;
import org.apache.brooklyn.core.sensor.Sensors;
import org.apache.brooklyn.core.test.BrooklynMgmtUnitTestSupport;
import org.apache.brooklyn.core.workflow.steps.external.SshWorkflowStep;
import org.apache.brooklyn.entity.stock.BasicApplication;
import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
import org.apache.brooklyn.location.ssh.SshMachineLocation;
import org.apache.brooklyn.test.Asserts;
import org.apache.brooklyn.util.collections.MutableList;
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.core.internal.ssh.RecordingSshTool;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.http.executor.HttpConfig;
import org.apache.brooklyn.util.net.Networking;
import org.apache.brooklyn.util.text.Strings;
import org.apache.brooklyn.util.time.Duration;
import org.testng.annotations.Test;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.stream.Collectors;
public class WorkflowBeefyStepTest extends BrooklynMgmtUnitTestSupport {
protected void loadTypes() {
WorkflowBasicTest.addWorkflowStepTypes(mgmt);
}
BasicApplication lastApp;
Object runStep(Object step, Consumer<BasicApplication> appFunction) {
return runSteps(MutableList.<Object>of(step), appFunction);
}
Object runSteps(List<Object> steps, Consumer<BasicApplication> appFunction) {
return runSteps(steps, appFunction, null);
}
Object runSteps(List<Object> steps, Consumer<BasicApplication> appFunction, ConfigBag defaultConfig) {
return runSteps(true, steps, appFunction, defaultConfig);
}
Object runMoreSteps(List<Object> steps) {
return runSteps(false, steps, null, null);
}
Object runSteps(boolean reset, List<Object> steps, Consumer<BasicApplication> appFunction, ConfigBag defaultConfig) {
loadTypes();
BasicApplication app = reset || lastApp==null ? mgmt.getEntityManager().createEntity(EntitySpec.create(BasicApplication.class)) : lastApp;
this.lastApp = app;
WorkflowEffector eff = new WorkflowEffector(ConfigBag.newInstance()
.configure(WorkflowEffector.EFFECTOR_NAME, "myWorkflow")
.configure(WorkflowEffector.EFFECTOR_PARAMETER_DEFS, MutableMap.of("p1", MutableMap.of("defaultValue", "p1v")))
.configure(WorkflowEffector.STEPS, steps)
.putAll(defaultConfig)
);
if (appFunction!=null) appFunction.accept(app);
eff.apply((EntityLocal)app);
Task<?> invocation = app.invoke(app.getEntityType().getEffectorByName("myWorkflow").get(), null);
return invocation.getUnchecked();
}
@Test
public void testEffector() {
Object result = runSteps(MutableList.of(
"let x = ${entity.sensor.x} + 1 ?? 0",
"set-sensor x = ${x}",
"set-sensor myWorkflow-ret-type = ${entity.effector[\"myWorkflow\"].returnType}",
"set-sensor last-param = ${p1}",
MutableMap.of(
"s", "invoke-effector myWorkflow",
"args", MutableMap.of("p1", "from-invocation"),
"condition", MutableMap.of("target", "${x}", "less-than", 2),
"next", "end"),
MutableMap.of(
"s", "invoke-effector nonExistentEffector",
"condition", MutableMap.of("target", "${entity.effector[\"nonExistentEffector\"]}")),
"return ${x}" // if effector isn't invoked
), null);
Asserts.assertEquals(result, 2);
EntityAsserts.assertAttributeEquals(lastApp, Sensors.newSensor(Object.class, "x"), 2);
EntityAsserts.assertAttributeEquals(lastApp, Sensors.newSensor(Object.class, "last-param"), "from-invocation");
EntityAsserts.assertAttributeEquals(lastApp, Sensors.newSensor(Object.class, "myWorkflow-ret-type"), Object.class);
}
@Test
public void testSensorMap() throws Exception {
Object r;
r = runSteps(MutableList.of("set-sensor some.map['key'] = x", "return ${entity.sensor['some.map']}"), null);
Asserts.assertEquals(r, MutableMap.of("key", "x"));
r = runMoreSteps(MutableList.of("set-sensor some.map[key2] = y", "return ${entity.sensor['some.map']}"));
Asserts.assertEquals(r, MutableMap.of("key", "x", "key2", "y"));
r = runMoreSteps(MutableList.of("set-sensor some.new['a'][\"b\"][-1] = ab0", "return ${entity.sensor['some.new']}"));
Asserts.assertEquals(r, MutableMap.of("a", MutableMap.of("b", MutableList.of("ab0"))));
r = runMoreSteps(MutableList.of("set-sensor some.new[\"a\"][\"b\"][1] = ab1", "return ${entity.sensor['some.new']}"));
Asserts.assertEquals(r, MutableMap.of("a", MutableMap.of("b", MutableList.of("ab0", "ab1"))));
r = runMoreSteps(MutableList.of("clear-sensor some.new[\"a\"][\"b\"][0]", "return ${entity.sensor['some.new']}"));
Asserts.assertEquals(r, MutableMap.of("a", MutableMap.of("b", MutableList.of("ab1"))));
r = runMoreSteps(MutableList.of("clear-sensor some.new[\"a\"][\"b\"][999]", "return ${entity.sensor['some.new']}"));
Asserts.assertEquals(r, MutableMap.of("a", MutableMap.of("b", MutableList.of("ab1"))));
r = runMoreSteps(MutableList.of("clear-sensor some.new[\"a\"]", "return ${entity.sensor['some.new']}"));
Asserts.assertEquals(r, MutableMap.of());
}
@Test(groups="Integration")
public void testShell() {
Object result = runStep("shell echo foo", null);
Asserts.assertEquals(result, MutableMap.of("exit_code", 0, "stdout", "foo\n", "stderr", ""));
}
@Test
public void testSshLocalhost() throws NoMachinesAvailableException {
LocalhostMachineProvisioningLocation loc = mgmt.getLocationManager().createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class)
.configure("address", Networking.getReachableLocalHost())
.configure(SshMachineLocation.SSH_TOOL_CLASS, RecordingSshTool.class.getName()));
SshMachineLocation ll = loc.obtain();
RecordingSshTool.setCustomResponse(".*", new RecordingSshTool.CustomResponse(0, "foo", "<testing stderr>"));
Object result = runStep("ssh echo foo", app -> ((EntityInternal) app).addLocations(MutableList.of(ll)));
Asserts.assertEquals(RecordingSshTool.getExecCmds().stream().map(ex -> ex.commands).collect(Collectors.toList()), MutableList.of(MutableList.of("echo foo")));
Asserts.assertEquals(result, MutableMap.of("exit_code", 0, "stdout", "foo", "stderr", "<testing stderr>"));
}
@Test
public void testSshTruncate() {
Asserts.assertEquals("... utput\n", SshWorkflowStep.truncate("hello world\nmore output\n", 10));
Asserts.assertEquals("hello world\nmore output\n", SshWorkflowStep.truncate("hello world\nmore output\n", 100));
Asserts.assertEquals("", SshWorkflowStep.truncate("hello world\nmore output\n", 0));
Asserts.assertEquals("put", SshWorkflowStep.truncate("hello world\nmore output", 3));
Asserts.assertEquals("tput", SshWorkflowStep.truncate("hello world\nmore output", 4));
Asserts.assertEquals("... t", SshWorkflowStep.truncate("hello world\nmore output", 5));
}
@Test
public void testHttp() throws IOException {
BetterMockWebServer server = BetterMockWebServer.newInstanceLocalhost();
server.enqueue(new MockResponse().setResponseCode(200).setBody("ack"));
server.play();
Map result = (Map) runStep("http "+server.getUrl("/"), null);
Asserts.assertEquals(result.get("status_code"), 200);
Asserts.assertEquals(result.get("content"), "ack");
Asserts.assertEquals(new String((byte[])result.get("content_bytes")), "ack");
Asserts.assertThat(result.get("duration"), x -> Duration.nanos(1).isShorterThan(Duration.of(x)));
}
@Test(groups="Integration") //requires internet
public void testHttps() throws IOException {
doTestHttpsGoogle("https://www.google.com", null, true);
doTestHttpsGoogle("www.google.com", null, true);
// IP of google won't work unless we trust it
doTestHttpsGoogle("172.217.169.68", null, false);
doTestHttpsGoogle("172.217.169.68", MutableMap.of("config", HttpConfig.builder().trustAll(true).build()), true);
doTestHttpsGoogle("172.217.169.68", MutableMap.of("config", MutableMap.of("trustAll", true)), true);
doTestHttpsGoogle("172.217.169.68", MutableMap.of("config", MutableMap.of("trustAll", false)), false);
}
public Map doTestHttpsGoogle(String url, Map<String, Object> extraConfig, Boolean shouldWork) {
Map result = null;
try {
result = (Map) runStep(MutableMap.<String, Object>of("s", "http " + url).add(extraConfig), null);
if (shouldWork == null) {
// no op, just return result
} else if (shouldWork) {
Asserts.assertEquals(result.get("status_code"), 200);
MutableList.of("" + result.get("content"), "" + new String((byte[]) result.get("content_bytes"))).forEach(s ->
Asserts.assertStringContains(s, "<html", "google.timers.load"));
} else {
Asserts.shouldHaveFailedPreviously("Instead got: " + result);
}
} catch (Exception e) {
if (Boolean.FALSE.equals(shouldWork)) {
// expected, just make sure it isn't the "should have failed" exception
Asserts.expectedFailure(e);
} else {
Asserts.fail(e);
}
}
return result;
}
// container, winrm defined in downstream projects and tested in those projects and/or workflow yaml
/*
* TODO - sensor+policy - triggers from children / members
*
* TODO - custom ssh endpoint
* TODO - ? - custom cert logic for http
*
* TODO - copying scp, kubecp ?; http put from file?; and filesets?
* ... or ... stream-from: xxx; but that is too fiddly. support writing to temp file for use with cli?
* xcp [ [?${FROM} [?${FILESET} "fileset"] ${LOCAL}] ${REMOTE_FILE_OR_PATH}
*
* type: scp
* from:
* - bundle: xxxx
* glob: ** / *.tf
* to: path/
* mkdir: true
* rmdir: true
*
* output:
* contents - if one argument supplied, receive that data, allow copy `from: { data: ${value} }`
* count - number of files copied
*/
}