blob: e411031ac0a4f27f3319ce91440017c72455a9cc [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 java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.api.mgmt.Task;
import org.apache.brooklyn.api.typereg.RegisteredType;
import org.apache.brooklyn.core.entity.EntityAsserts;
import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
import org.apache.brooklyn.core.mgmt.rebind.RebindOptions;
import org.apache.brooklyn.core.mgmt.rebind.RebindTestFixture;
import org.apache.brooklyn.core.resolve.jackson.BeanWithTypePlanTransformer;
import org.apache.brooklyn.core.sensor.Sensors;
import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
import org.apache.brooklyn.core.test.entity.TestApplication;
import org.apache.brooklyn.core.typereg.BasicTypeImplementationPlan;
import org.apache.brooklyn.core.workflow.steps.CustomWorkflowStep;
import org.apache.brooklyn.core.workflow.store.WorkflowRetentionAndExpiration;
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.text.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.Test;
public class WorkflowSubIfAndCustomExtensionEdgeTest extends RebindTestFixture<TestApplication> {
private static final Logger log = LoggerFactory.getLogger(WorkflowSubIfAndCustomExtensionEdgeTest.class);
@Override
protected LocalManagementContext decorateOrigOrNewManagementContext(LocalManagementContext mgmt) {
WorkflowBasicTest.addWorkflowStepTypes(mgmt);
app = null; // clear this
mgmt.getBrooklynProperties().put(WorkflowRetentionAndExpiration.WORKFLOW_RETENTION_DEFAULT, "forever");
return super.decorateOrigOrNewManagementContext(mgmt);
}
@Override
protected TestApplication createApp() {
return null;
}
@Override protected TestApplication rebind() throws Exception {
return rebind(RebindOptions.create().terminateOrigManagementContext(true));
}
public RegisteredType addBeanWithType(String typeName, String version, String plan) {
return BrooklynAppUnitTestSupport.addRegisteredTypeBean(mgmt(), typeName, version,
new BasicTypeImplementationPlan(BeanWithTypePlanTransformer.FORMAT, plan));
}
TestApplication app;
Task<?> lastInvocation;
Object runWorkflow(List<Object> steps) {
return runWorkflow(steps, null);
}
Object runWorkflow(List<Object> steps, ConfigBag extraEffectorConfig) {
if (app==null) app = mgmt().getEntityManager().createEntity(EntitySpec.create(TestApplication.class));
WorkflowEffector eff = new WorkflowEffector(ConfigBag.newInstance()
.configure(WorkflowEffector.EFFECTOR_NAME, "myWorkflow")
.configure(WorkflowEffector.STEPS, steps)
.putAll(extraEffectorConfig));
eff.apply(app);
lastInvocation = app.invoke(app.getEntityType().getEffectorByName("myWorkflow").get(), null);
return lastInvocation.getUnchecked();
}
@Test
public void testVisibilityOfReducingWhenDefiningCustomWorkflowStep() throws Exception {
// test the fixture is right
MutableMap<String, Serializable> basicReducingStep = MutableMap.of(
"type", "workflow",
"reducing", MutableMap.of("hello_word", "${hello_word}"),
"steps", MutableList.of("set-sensor hi = ${hello_word} world"));
runWorkflow(MutableList.of("let hello_word = HI", basicReducingStep));
EntityAsserts.assertAttributeEquals(app, Sensors.newSensor(String.class, "hi"), "HI world");
addBeanWithType("set-sensor-hi-reducing", "1-SNAPSHOT", Strings.lines(
"type: workflow",
"parameters:",
" value: {}",
"reducing:",
" hello_word: ${hello_word}",
"steps:",
" - let hi_word = ${hello_word} ?? hi",
" - set-sensor hi = ${hi_word} ${value}",
" - let hello_word = bye"
));
if (CustomWorkflowStep.CUSTOM_WORKFLOW_STEP_REGISTERED_TYPE_EXTENSIONS_CAN_REDUCE) {
runWorkflow(MutableList.of(
"let hello_word = HELLO",
MutableMap.of("type", "set-sensor-hi-reducing", "input", MutableMap.of("value", "bob")),
"return ${hello_word}"));
Asserts.assertEquals(lastInvocation.getUnchecked(), "bye");
EntityAsserts.assertAttributeEquals(app, Sensors.newSensor(String.class, "hi"), "HELLO bob");
} else {
Asserts.assertFailsWith(() -> runWorkflow(MutableList.of(
"let hello_word = HELLO",
MutableMap.of("type", "set-sensor-hi-reducing", "input", MutableMap.of("value", "bob")),
"return ${hello_word}")),
Asserts.expectedFailureContainsIgnoreCase("not permitted", "reducing"));
}
addBeanWithType("set-sensor-hi", "1-SNAPSHOT", Strings.lines(
"type: workflow",
"parameters:",
" value: {}",
"steps:",
" - set-sensor hi = hi ${value}"
));
// value is a poor choice with set-sensor, because set-sensor tries to evaluate the input; but let's make sure the message is not too confusing
Asserts.assertFailsWith(() -> runWorkflow(MutableList.of(
"let hello_word = HI",
"set-sensor-hi")),
Asserts.expectedFailureContainsIgnoreCase("recursive or missing reference","value")
);
}
@Test
public void testSubWorkflowStep() throws Exception {
Function<Boolean,Object> test = (explicitSubworkflow) ->
runWorkflow(MutableList.of(
"let v1 = V1",
"let v2 = V2",
MutableMap.<String,Object>of("steps", MutableList.of(
"let v0 = ${v0}B",
"let v1 = ${v1}B",
"let v3 = V3B"))
.add(explicitSubworkflow ? MutableMap.of("step", "subworkflow") : null),
"return ${v0}-${v1}-${v2}-${v3}"),
ConfigBag.newInstance().configure(WorkflowCommonConfig.INPUT, MutableMap.of("v0", "V0")) );
Asserts.assertEquals(test.apply(true), "V0B-V1B-V2-V3B");
// subworkflow is chosen implicitly if step is omitted
Asserts.assertEquals(test.apply(false), "V0B-V1B-V2-V3B");
runWorkflow(MutableList.of(
MutableMap.of("steps", MutableList.of(
"let v1 = V1",
"goto marker", // prefers inner id 'marker'
"let v1 = NOT_V1_1",
MutableMap.of("id", "marker", "step", "goto end"), // goes to end of this subworkflow
"let v1 = NOT_V1_2")),
"let v2 = V2",
MutableMap.of("steps", MutableList.of(
"let v3 = V3",
"goto marker", // goes to outer id 'marker' because nothing local matching
"let v3 = NOT_V3")),
MutableMap.of("id", "marker", "step", "let v4 = V4"),
MutableMap.of("steps", MutableList.of(
"return ${v1}-${v2}-${v3}-${v4}")), // returns from outer workflow
"let v4 = NOT_V4"));
Asserts.assertEquals(lastInvocation.getUnchecked(), "V1-V2-V3-V4");
}
@Test
public void testIfWorkflowStep() throws Exception {
BiFunction<String,String,Object> run = (preface,cond) ->
runWorkflow(MutableList.<Object>of(preface==null ? "let x = hi" : preface,
"let y = no",
"if "+(cond==null ? "${x}" : cond)+" then let y = yes",
"return ${y}"));
Asserts.assertEquals(run.apply(null, null), "yes");
Asserts.assertEquals(run.apply("let boolean x = true", null), "yes");
Asserts.assertEquals(run.apply("let boolean x = false", null), "no");
Asserts.assertEquals(run.apply(null, "${x} == hi"), "yes");
Asserts.assertEquals(run.apply(null, "${x} == ho"), "no");
Asserts.assertEquals(run.apply(null, "hi == ${x}"), "yes");
Asserts.assertEquals(run.apply(null, "${x} == ${x}"), "yes");
Asserts.assertEquals(run.apply("let boolean x = true", "${x} == true"), "yes");
Asserts.assertEquals(run.apply("set-sensor xy = yes", "{ sensor: xy, equals: yes }"), "yes");
Asserts.assertEquals(run.apply(null, "{ sensor: xn, equals: yes }"), "no");
Asserts.assertFailsWith(() -> run.apply(null, "{ unknown_field: xn }"),
Asserts.expectedFailureContainsIgnoreCase("unknown_field", "predicate"));
// unresolvable things -- allowed iff no == block
Asserts.assertEquals(run.apply(null, "${unresolvable_without_equals}"), "no");
Asserts.assertFailsWith(() -> run.apply(null, "${x} == ${unresolvable_on_rhs}"),
Asserts.expectedFailureContainsIgnoreCase("unresolvable_on_rhs", "missing"));
Asserts.assertFailsWith(() -> run.apply(null, "${unresolvable_on_lhs} == ${x}"),
Asserts.expectedFailureContainsIgnoreCase("unresolvable_on_lhs", "missing"));
// flow - returns directly
Asserts.assertEquals(runWorkflow(MutableList.of("let boolean x = true",
"if ${x} then return yes",
"return no")), "yes");
Asserts.assertEquals(runWorkflow(MutableList.of("let integer x = 1",
MutableMap.of("id", "loop", "step", "let x = ${x} + 1"),
"if ${x} == 2 then goto loop",
"return ${x}")), 3);
}
@Test
public void testIfWorkflowWithSteps() throws Exception {
BiFunction<String, String, Object> run = (preface, cond) ->
runWorkflow(MutableList.<Object>of(preface==null ? "let x = hi" : preface,
MutableMap.of("step", "if "+(cond==null ? "${x}" : cond),
"steps", MutableList.of("let y = yes", "return ${y}")),
"return no"));
Asserts.assertEquals(run.apply(null, null), "yes");
Asserts.assertEquals(run.apply("let boolean x = false", null), "no");
}
}