/*
* 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.entity.software.base;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.api.location.Location;
import org.apache.brooklyn.core.entity.Attributes;
import org.apache.brooklyn.core.entity.Entities;
import org.apache.brooklyn.core.test.entity.TestApplication;
import org.apache.brooklyn.entity.software.base.SoftwareProcess.ChildStartableMode;
import org.apache.brooklyn.test.EntityTestUtils;
import org.apache.brooklyn.util.core.ResourceUtils;
import org.apache.brooklyn.util.javalang.JavaClassNames;
import org.apache.brooklyn.util.os.Os;
import org.apache.brooklyn.util.text.Strings;
import org.apache.brooklyn.util.time.Duration;
import org.apache.brooklyn.util.time.Time;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.google.common.base.Stopwatch;
public class VanillaSoftwareProcessAndChildrenIntegrationTest {
// TODO Why are these tests so slow? Even when the sleep time was 3 seconds instead of 10, they would still take about 10 seconds.
// Note that tests run by jenkins can be extremely time-sensitive.
// e.g. http://brooklyn.builds.cloudsoftcorp.com/job/Brooklyn-Master-Integration/io.brooklyn$brooklyn-software-base/217/testReport/junit/org.apache.brooklyn.entity.software.base/VanillaSoftwareProcessAndChildrenIntegrationTest/testModeBackground/
// shows a 5 second difference when in background mode, whereas the test originally asserted a difference of <= 1.
// Therefore increasing time that tests will take, but decreasing the sensitivity so we don't get such false-negatives.
private static final Logger log = LoggerFactory.getLogger(VanillaSoftwareProcessAndChildrenIntegrationTest.class);
private static final int PARENT_TASK_SLEEP_LENGTH_SECS = 10;
private static final int CHILD_TASK_SLEEP_LENGTH_SECS = 10;
private static final int CONCURRENT_MAX_ACCEPTABLE_DIFF_SECS = PARENT_TASK_SLEEP_LENGTH_SECS - 1;
private static final int SEQUENTIAL_MIN_ACCEPTABLE_DIFF_SECS = PARENT_TASK_SLEEP_LENGTH_SECS - 1;
private static final int EARLY_RETURN_GRACE_MS = 20;
private TestApplication app;
private Location localhost;
private VanillaSoftwareProcess p1;
private VanillaSoftwareProcess p2;
@BeforeMethod(alwaysRun=true)
public void setup() {
app = TestApplication.Factory.newManagedInstanceForTests();
localhost = app.getManagementContext().getLocationRegistry().resolve("localhost");
}
@AfterMethod(alwaysRun=true)
public void shutdown() {
if (app != null) Entities.destroyAll(app.getManagementContext());
}
@Test(groups = "Integration")
public void testModeNone() {
prep(ChildStartableMode.NONE);
long startTime = startApp();
Assert.assertNotNull(p1.getAttribute(SoftwareProcess.RUN_DIR));
Assert.assertNull(p2.getAttribute(SoftwareProcess.RUN_DIR));
Assert.assertTrue(startTime >= PARENT_TASK_SLEEP_LENGTH_SECS*1000 - EARLY_RETURN_GRACE_MS, "startTime="+Time.makeTimeStringRounded(startTime));
}
@Test(groups = "Integration")
public void testModeForeground() {
prep(ChildStartableMode.FOREGROUND);
long startTime = startApp();
long timediff = timediff();
Assert.assertTrue( Math.abs(timediff) <= CONCURRENT_MAX_ACCEPTABLE_DIFF_SECS, "should have started concurrently, not with time difference "+timediff+" ("+p1+", "+p2+")" );
Assert.assertTrue(startTime >= PARENT_TASK_SLEEP_LENGTH_SECS*1000 - EARLY_RETURN_GRACE_MS, "startTime="+Time.makeTimeStringRounded(startTime));
}
@Test(groups = "Integration")
public void testModeForegroundLate() {
prep(ChildStartableMode.FOREGROUND_LATE);
long startTime = startApp();
long timediff = timediff();
Assert.assertTrue( timediff >= SEQUENTIAL_MIN_ACCEPTABLE_DIFF_SECS, "should have started later, not with time difference "+timediff+" ("+p1+", "+p2+")" );
Assert.assertTrue(startTime >= 2*PARENT_TASK_SLEEP_LENGTH_SECS*1000 - EARLY_RETURN_GRACE_MS, "startTime="+Time.makeTimeStringRounded(startTime));
}
@Test(groups = "Integration")
public void testModeBackground() {
prep(ChildStartableMode.BACKGROUND);
long startTime = startApp();
checkChildComesUpSoon();
long timediff = timediff();
Assert.assertTrue( Math.abs(timediff) <= CONCURRENT_MAX_ACCEPTABLE_DIFF_SECS, "should have started concurrently, not with time difference "+timediff+" ("+p1+", "+p2+")" );
Assert.assertTrue(startTime >= PARENT_TASK_SLEEP_LENGTH_SECS*1000 - EARLY_RETURN_GRACE_MS, "startTime="+Time.makeTimeStringRounded(startTime));
}
@Test(groups = "Integration")
public void testModeBackgroundLate() {
prep(ChildStartableMode.BACKGROUND_LATE);
long startTime = startApp();
checkChildNotUpYet();
checkChildComesUpSoon();
long timediff = timediff();
Assert.assertTrue( Math.abs(timediff) >= SEQUENTIAL_MIN_ACCEPTABLE_DIFF_SECS, "should have started later, not with time difference "+timediff+" ("+p1+", "+p2+")" );
Assert.assertTrue(startTime >= PARENT_TASK_SLEEP_LENGTH_SECS*1000 - EARLY_RETURN_GRACE_MS, "startTime="+Time.makeTimeStringRounded(startTime));
// just to prevent warnings
waitForBackgroundedTasks(CHILD_TASK_SLEEP_LENGTH_SECS+1);
app.stop();
app = null;
}
private long startApp() {
Stopwatch stopwatch = Stopwatch.createStarted();
app.start(Collections.singleton(localhost));
long result = stopwatch.elapsed(TimeUnit.MILLISECONDS);
log.info("Took "+Time.makeTimeStringRounded(result)+" for app.start to complete");
return result;
}
private void waitForBackgroundedTasks(int secs) {
// child task is backgrounded; quick and dirty way to make sure it finishes (after setting service_up)
Time.sleep(Duration.seconds(secs));
}
private void checkChildNotUpYet() {
Assert.assertFalse(p2.getAttribute(SoftwareProcess.SERVICE_UP));
}
private void checkChildComesUpSoon() {
Stopwatch stopwatch = Stopwatch.createStarted();
EntityTestUtils.assertAttributeEqualsEventually(p2, Attributes.SERVICE_UP, true);
log.info("Took "+Time.makeTimeStringRounded(stopwatch)+" for child-process to be service-up");
}
private long timediff() {
Long d1 = getRunTimeUtc(p1);
Long d2 = getRunTimeUtc(p2);
log.info("timestamps for "+JavaClassNames.callerNiceClassAndMethod(1)+" have difference "+(d2-d1));
return d2 - d1;
}
private Long getRunTimeUtc(VanillaSoftwareProcess p) {
Assert.assertNotNull(p.getAttribute(SoftwareProcess.RUN_DIR));
return Long.parseLong( Strings.getFirstWordAfter(new ResourceUtils(this).getResourceAsString(Os.mergePaths(p.getAttribute(SoftwareProcess.RUN_DIR), "DATE")), "utc") );
}
private void prep(ChildStartableMode mode) {
String parentCmd = "echo utc `date +%s` > DATE ; echo human `date` >> DATE ; "
+ "{ nohup sleep 60 & } ; echo $! > $PID_FILE ; sleep "+PARENT_TASK_SLEEP_LENGTH_SECS;
String childCmd = "echo utc `date +%s` > DATE ; echo human `date` >> DATE ; "
+ "{ nohup sleep 60 & } ; echo $! > $PID_FILE ; sleep "+CHILD_TASK_SLEEP_LENGTH_SECS;
p1 = app.createAndManageChild(EntitySpec.create(VanillaSoftwareProcess.class)
.configure(VanillaSoftwareProcess.LAUNCH_COMMAND, parentCmd)
.configure(VanillaSoftwareProcess.CHILDREN_STARTABLE_MODE, mode)
);
p2 = p1.addChild(EntitySpec.create(VanillaSoftwareProcess.class)
.configure(VanillaSoftwareProcess.LAUNCH_COMMAND, childCmd));
log.info("testing "+JavaClassNames.callerNiceClassAndMethod(1)+", using "+p1+" and "+p2);
}
}