Skip to content
Permalink
Browse files
Merge branch 'master' into blueprint-for-app-spec
  • Loading branch information
ahgittin committed Jan 13, 2016
2 parents 0049626 + e2392c3 commit 0781c1032b11e894cdb437ee7b7facb927964288
Showing 28 changed files with 1,162 additions and 127 deletions.
@@ -6,20 +6,24 @@ children:
- usage-examples.md
---

Brooklyn provides a selection of basic test entities which can be used to validate Blueprints via YAML. These are divided into two groups; structural, which effect the order in which child entities are started; and validation, which are used to confirm the application is deployed as intended.
Brooklyn provides a selection of test entities which can be used to validate Blueprints via YAML. The basic building block is a TargetableTestComponent, which is used to resolve a target. There are two different groups of entities that inherit from TargetableTestComponent. The first is structural, which effects how the tests are run, for example by affecting the order they are run in. The second group is validation, which is used to confirm the application is deployed as intended, for example by checking some sensor value.

Structural test entities include:

- `TestCase` - starts child entities sequentially.
- `ParallelTestCase` - starts child entities in parallel.
- `LoopOverGroupMembersTestCase` - creates a TargetableTestComponent for each member of a group.
- `InfrastructureDeploymentTestCase` - will create the specified Infrastructure and then deploy the target entity specifications there.

Validation test entities include:

- `TestSensor` - perform assertion on a specified sensor.
- `TestEffector` - invoke effector on specified target entity.
- `TestEffector` - perform assertion on response to effector call.
- `TestHttpCall` - perform assertion on response to specified HTTP GET Request.
- `SimpleShellCommandTest` - test assertions on the result of a shell command on the same node as the target entity.

TargetableTestComponents can be chained together, with the target being inherited by the components children. For example, a ParallelTestCase could be created that has a TestHttpCall as a child. As long as the TestHttpCall itself does not have a target, it will use the target of it's parent, ParallelTestCase. Using this technique, we can build up complex test scenarios.

The following sections provide details on each test entity along with examples of their use.

{% include list-children.html %}
@@ -33,6 +33,32 @@ This can be used to run a subset of entities in parallel as a single step when n

Timeouts on child entities should be set relative to the start of the `ParallelTestCase`.

### LoopOverGroupMembersTestCase
The `LoopOverGroupMembersTestCase` entity is configured with a target group and a test specification. For each member of the targeted group, the test case will create a TargetableTestComponent entity from the supplied test specification and set the components target to be the group member.

{% highlight yaml %}
{% readj example_yaml/entities/loopovergroupmembers-entity.yaml %}
{% endhighlight %}

#### Parameters
- `target` - group who's members are to be tested, specified via DSL. For example, `$brooklyn:component("tomcat")`. See also the `targetId` parameter.
- `targetId` - alternative to the `target` parameter which wraps the DSL component lookup requiring only the `id` be supplied. For example, `tomcat`. Please note, this must point to a group.
- `test.spec` - The TargetableTestComponent to create for each child.


### InfrastructureDeploymentTestCase
The `InfrastructureDeploymentTestCase` will first create and deploy an infrastructure from the `infrastructure.deployment.spec` config. It will then retrieve a deployment location by getting the value of the infrastructures `infrastructure.deployment.location.sensor` sensor. It will then create and deploy all entities from the `infrastructure.deployment.spec` config to the deployment location.

{% highlight yaml %}
{% readj example_yaml/entities/infrastructuredeploymenttestcase-entity.yaml %}
{% endhighlight %}

#### Parameters

- `infrastructure.deployment.spec` - the infrastructure to be deployed.
- `infrastructure.deployment.entity.specs` - the entities to be deployed to the infrastructure
- `infrastructure.deployment.location.sensor` - the name of the sensor on the infrastructure to retrieve the deployment location

## Validation Test Entities

### TestSensor
@@ -50,7 +76,7 @@ The `TestSensor` entity performs an assertion on a specified sensors value.
- `assert` - assertion to perform on the specified sensor value. See section on assertions below.

### TestEffector
The `TestEffector` entity invokes the specified effector on a target entity.
The `TestEffector` entity invokes the specified effector on a target entity. If the result of the effector is a String, it will then perform assertions on the result.
{% highlight yaml %}
{% readj example_yaml/entities/testeffector-entity.yaml %}
{% endhighlight %}
@@ -61,6 +87,7 @@ The `TestEffector` entity invokes the specified effector on a target entity.
- `timeout` - duration to wait on the effector task to complete. For example `10s`, `10m`, etc
- `effector` - effector to invoke, for example `deploy`.
- `params` - parameters to pass to the effector, these will depend on the entity and effector being tested. The example above shows the `url` and `targetName` parameters being passed to Tomcats `deploy` effector.
- `assert` - assertion to perform on the returned result. See section on assertions below.

### TestHttpCall
The `TestHttpCall` entity performs a HTTP GET on the specified URL and performs an assertion on the response.
@@ -77,7 +104,7 @@ The `TestHttpCall` entity performs a HTTP GET on the specified URL and performs
### SimpleShellCommandTest

The SimpleShellCommandTest runs a command on the host of the target entity.
The script is expected not to run indefinitely, but to return a result (process exit code), along with its
The script is expected not to run indefinitely, but to return a result (process exit code), along with its
standard out and error streams, which can then be tested using assertions.
If no assertions are explicitly configured, the default is to assert a non-zero exit code.

@@ -119,7 +146,7 @@ Assertions may be provided as a simple map:
matches: .*[\d]* days.*
```

If there is the need to make multiple assertions with the same key, the assertions can be specified
If there is the need to make multiple assertions with the same key, the assertions can be specified
as a list of such maps:

```yaml
@@ -18,34 +18,20 @@
*/
package org.apache.brooklyn.test.framework;

import java.util.Map;
import java.util.concurrent.TimeUnit;

import com.google.common.collect.ImmutableList;
import com.google.common.reflect.TypeToken;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.mgmt.ExecutionContext;

import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.config.ConfigKeys;
import org.apache.brooklyn.core.entity.trait.Startable;
import org.apache.brooklyn.util.time.Duration;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
* A base interface for all tests.
*/
public interface BaseTest extends Entity, Startable {

/**
* The target entity to test (optional, use either this or targetId).
*/
ConfigKey<Entity> TARGET_ENTITY = ConfigKeys.newConfigKey(Entity.class, "target", "Entity under test");

/**
* Id of the target entity to test (optional, use either this or target).
*/
ConfigKey<String> TARGET_ID = ConfigKeys.newStringConfigKey("targetId", "Id of the entity under test");
public interface BaseTest extends TargetableTestComponent, Startable {

/**
* The assertions to be made.
@@ -59,12 +45,5 @@ public interface BaseTest extends Entity, Startable {
ConfigKey<Duration> TIMEOUT = ConfigKeys.newConfigKey(Duration.class, "timeout", "Time to wait on result",
new Duration(1L, TimeUnit.SECONDS));

/**
* Get the target of the test.
*
* @return The target.
* @throws IllegalArgumentException if the target cannot be found.
*/
Entity resolveTarget();

}
@@ -18,7 +18,10 @@
*/
package org.apache.brooklyn.test.framework;

import java.util.List;

import com.google.common.reflect.TypeToken;

import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.api.entity.ImplementedBy;
import org.apache.brooklyn.config.ConfigKey;
@@ -30,19 +33,19 @@
* Created by graememiller on 04/12/2015.
*/
@ImplementedBy(value = InfrastructureDeploymentTestCaseImpl.class)
public interface InfrastructureDeploymentTestCase extends TestCase {
public interface InfrastructureDeploymentTestCase extends TargetableTestComponent {

/**
* Entity spec to deploy. This will be deployed second, after the INFRASTRUCTURE_SPEC has been deployed. This will be deployed to the DEPLOYMENT_LOCATION.
* Entity specs to deploy. These will be deployed second, after the INFRASTRUCTURE_SPEC has been deployed. These specs will be deployed to the DEPLOYMENT_LOCATION.
* All children will be deployed after this
*/
ConfigKey<EntitySpec<SoftwareProcess>> ENTITY_SPEC_TO_DEPLOY = ConfigKeys.newConfigKey(new TypeToken<EntitySpec<SoftwareProcess>>(){}, "infrastructure.deployment.entity.spec", "Entity spec to deploy to infrastructure");
ConfigKey<List<EntitySpec<? extends SoftwareProcess>>> ENTITY_SPEC_TO_DEPLOY = ConfigKeys.newConfigKey(new TypeToken<List<EntitySpec<? extends SoftwareProcess>>>(){}, "infrastructure.deployment.entity.specs", "Entity specs to deploy to infrastructure");


/**
* Infrastructure to deploy. This will be deployed first, then the ENTITY_SPEC_TO_DEPLOY will be deployed, then any children
*/
ConfigKey<EntitySpec<StartableApplication>> INFRASTRUCTURE_SPEC = ConfigKeys.newConfigKey(new TypeToken<EntitySpec<StartableApplication>>(){}, "infrastructure.deployment.spec", "Infrastructure to deploy");
ConfigKey<EntitySpec<? extends StartableApplication>> INFRASTRUCTURE_SPEC = ConfigKeys.newConfigKey(new TypeToken<EntitySpec<? extends StartableApplication>>(){}, "infrastructure.deployment.spec", "Infrastructure to deploy");


/**
@@ -18,40 +18,76 @@
*/
package org.apache.brooklyn.test.framework;

import java.util.Collection;
import java.util.List;

import com.google.common.collect.ImmutableList;

import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.api.location.Location;
import org.apache.brooklyn.core.annotation.EffectorParam;
import org.apache.brooklyn.core.entity.Attributes;
import org.apache.brooklyn.core.entity.StartableApplication;
import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
import org.apache.brooklyn.core.sensor.Sensors;
import org.apache.brooklyn.entity.software.base.SoftwareProcess;

import java.util.Collection;

/**
* Created by graememiller on 04/12/2015.
*/
public class InfrastructureDeploymentTestCaseImpl extends TestCaseImpl implements InfrastructureDeploymentTestCase {

@Override
public void start(@EffectorParam(name = "locations") Collection<? extends Location> locations) {
setServiceState(false, Lifecycle.STARTING);

//Create the infrastructure
EntitySpec<StartableApplication> infrastructureSpec = config().get(INFRASTRUCTURE_SPEC);
EntitySpec<? extends StartableApplication> infrastructureSpec = config().get(INFRASTRUCTURE_SPEC);
if (infrastructureSpec == null) {
setServiceState(false, Lifecycle.ON_FIRE);
throw new IllegalArgumentException(INFRASTRUCTURE_SPEC + " not configured");
}

StartableApplication infrastructure = this.addChild(infrastructureSpec);
infrastructure.start(locations);

//Get the location
String deploymentLocationSensorName = config().get(DEPLOYMENT_LOCATION_SENSOR_NAME);
Location locationToDeployTo = infrastructure.sensors().get(Sensors.newSensor(Location.class, deploymentLocationSensorName));
if (deploymentLocationSensorName == null) {
setServiceState(false, Lifecycle.ON_FIRE);
throw new IllegalArgumentException(DEPLOYMENT_LOCATION_SENSOR_NAME + " not configured");
}

Location locationToDeployTo = infrastructure.sensors().get(Sensors.newSensor(Location.class, deploymentLocationSensorName));
if (locationToDeployTo == null) {
setServiceState(false, Lifecycle.ON_FIRE);
throw new IllegalArgumentException("Infrastructure does not have a location configured on sensor "+deploymentLocationSensorName);
}

//Start the child entity
EntitySpec<SoftwareProcess> entityToDeploySpec = config().get(ENTITY_SPEC_TO_DEPLOY);
SoftwareProcess entityToDeploy = this.addChild(entityToDeploySpec);
entityToDeploy.start(ImmutableList.of(locationToDeployTo));

List<EntitySpec<? extends SoftwareProcess>> entitySpecsToDeploy = config().get(ENTITY_SPEC_TO_DEPLOY);
if (entitySpecsToDeploy == null || entitySpecsToDeploy.isEmpty()) {
setServiceState(false, Lifecycle.ON_FIRE);
throw new IllegalArgumentException(ENTITY_SPEC_TO_DEPLOY + " not configured");
}
for (EntitySpec<? extends SoftwareProcess> softwareProcessEntitySpec : entitySpecsToDeploy) {
SoftwareProcess entityToDeploy = this.addChild(softwareProcessEntitySpec);
entityToDeploy.start(ImmutableList.of(locationToDeployTo));
}

//Defer to super class to start children
super.start(locations);
setServiceState(true, Lifecycle.RUNNING);
}

/**
* Sets the state of the Entity. Useful so that the GUI shows the correct icon.
*
* @param serviceUpState Whether or not the entity is up.
* @param serviceStateActual The actual state of the entity.
*/
private void setServiceState(final boolean serviceUpState, final Lifecycle serviceStateActual) {
sensors().set(Attributes.SERVICE_UP, serviceUpState);
sensors().set(Attributes.SERVICE_STATE_ACTUAL, serviceStateActual);
}
}
@@ -0,0 +1,45 @@
/*
* 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.test.framework;

import com.google.common.reflect.TypeToken;

import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.api.entity.ImplementedBy;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.config.ConfigKeys;
import org.apache.brooklyn.core.entity.trait.Startable;
import org.apache.brooklyn.util.core.flags.SetFromFlag;

/**
* Created by graememiller on 11/12/2015.
*/
@ImplementedBy(value = LoopOverGroupMembersTestCaseImpl.class)
public interface LoopOverGroupMembersTestCase extends TargetableTestComponent {

/**
* The test spec that will be run against each member of the group
*/
@SetFromFlag("testSpec")
ConfigKey<EntitySpec<? extends TargetableTestComponent>> TEST_SPEC = ConfigKeys.newConfigKey(
new TypeToken<EntitySpec<? extends TargetableTestComponent>>(){},
"test.spec",
"Test spec. The test case will create one of these per child");

}

0 comments on commit 0781c10

Please sign in to comment.