package org.cloudfoundry.community.servicebroker.datalifecycle.servicebinding;
import static com.jayway.restassured.RestAssured.given;
import static org.cloudfoundry.community.servicebroker.datalifecycle.config.LCCatalogConfig.COPY;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.notNullValue;
import java.sql.SQLException;
import java.util.Date;
import net.minidev.json.JSONObject;
import org.apache.http.entity.ContentType;
import org.cloudfoundry.community.servicebroker.datalifecycle.DataLifecycleServiceBrokerApplication;
import org.cloudfoundry.community.servicebroker.datalifecycle.repo.BindingRepository;
import org.cloudfoundry.community.servicebroker.model.ServiceInstance;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.http.HttpStatus;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import com.amazonaws.services.ec2.AmazonEC2Client;
import com.amazonaws.util.json.JSONException;
import com.jayway.restassured.RestAssured;
import com.jayway.restassured.builder.RequestSpecBuilder;
import com.jayway.restassured.response.Response;
import com.jayway.restassured.specification.RequestSpecification;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = DataLifecycleServiceBrokerApplication.class)
@WebAppConfiguration
@IntegrationTest("server.port:0")
@Category(IntegrationTest.class)
/**
* These tests assume an empty VPC with one instance (the prod one) running.
* You can set env variables to effect where and how the ec2Client behaves,
* see README.md in the project root
*
*/
public class BrokerIntegrationTest {
// TODO DRY w/ Catalog test
@Value("${local.server.port}")
private int port;
@Value("#{environment.SECURITY_USER_NAME}")
private String username;
@Value("#{environment.SECURITY_USER_PASSWORD}")
private String password;
@Autowired
private AmazonEC2Client ec2Client;
@Value("#{environment.SOURCE_INSTANCE_ID}")
private String sourceInstanceId;
@Autowired
private BindingRepository bindingRepo;
@Before
public void setUp() {
RestAssured.port = port;
RestAssured.requestSpecification = new RequestSpecBuilder().addHeader(
"X-Broker-Api-Version", "2.4").build();
bindingRepo.deleteAll();
}
@Test
public void itHasASourceInstance() {
given().auth().basic(username, password).get("/api/sourceinstance")
.then().body("sourceInstance", equalTo(sourceInstanceId));
}
@Test
public void itCreatesAnAMIAndImageAndCleansUp() throws Exception {
provisionAndBindCopy();
// Try to delete the wrong thing;
givenTheBroker()
.delete("/v2/service_instances/1234/service_bindings/nothing?service_id=lifecycle-sb&plan_id=copy")
.then().statusCode(410);
validateBinding();
unprovisionAndUnbindCopy();
givenTheBroker()
.delete("/v2/service_instances/1234?service_id=lifecycle-sb&plan_id=copy&accepts_incomplete=true")
.then().statusCode(410);
validateNothingProvisioned();
deleteScript();
}
private void unprovisionAndUnbindCopy() throws Exception {
givenTheBroker()
.delete("/v2/service_instances/1234/service_bindings/1234521?service_id=lifecycle-sb&plan_id=copy")
.then().statusCode(200);
validateNoBinding();
givenTheBroker()
.delete("/v2/service_instances/1234?service_id=lifecycle-sb&plan_id=copy&accepts_incomplete=true")
.then().statusCode(202);
validateNothingProvisioned();
}
private void provisionAndBindCopy() throws Exception {
validateNoBinding();
validateNothingProvisioned();
uploadScript();
JSONObject serviceInstance = new JSONObject();
serviceInstance.put("service_id", "lifecycle-sb");
serviceInstance.put("plan_id", COPY);
serviceInstance.put("organization_guid", "org_guid");
serviceInstance.put("space_guid", "s_guid");
givenTheBroker().and().contentType("application/json").and()
.body(serviceInstance.toString())
.put("/v2/service_instances/1234?accepts_incomplete=true")
.then().statusCode(202).and()
.body("last_operation.state", equalTo("in progress"));
validateProvisioning();
JSONObject args = new JSONObject();
args.put("service_id", "postgrescmd");
args.put("plan_id", COPY);
args.put("app_guid", "app_guid");
Response response = givenTheBroker().and()
.contentType("application/json").and().content(args.toString())
.and()
.put("/v2/service_instances/1234/service_bindings/1234521")
.then().statusCode(201).and().extract().response();
validateBinding();
validateSanitization(response.getBody().as(JSONObject.class));
Thread.sleep(10000); // Let AWS get the machines moving;
}
private void uploadScript() {
int i = (int) (new Date().getTime() / 1000);
JSONObject input = new JSONObject();
String script = "insert into touched VALUES (" + i + ", 1);";
input.put("script", script);
String location = "/api/sanitizescript";
given().auth().basic(username, password).and()
.content(input.toJSONString()).and()
.contentType(ContentType.APPLICATION_JSON.toString())
.post(location).then().statusCode(HttpStatus.CREATED.value())
.and().header("Location", containsString(location));
}
private void deleteScript() {
JSONObject input = new JSONObject();
input.put("script", "");
String location = "/api/sanitizescript";
given().auth().basic(username, password).and()
.content(input.toJSONString()).and()
.contentType(ContentType.APPLICATION_JSON.toString())
.post(location).then().statusCode(HttpStatus.CREATED.value())
.and().header("Location", containsString(location));
}
private RequestSpecification givenTheBroker() {
return given().auth().basic(username, password);
}
private void validateNothingProvisioned() throws Exception {
waitForDeProvisionCompletion();
givenTheBroker().get("/api/instances").then().body("$", hasSize(0));
givenTheBroker().get("/v2/service_instances/1234").then()
.statusCode(410);
}
private void validateNoBinding() throws Exception {
givenTheBroker().get("/api/bindings").then().body("$", hasSize(0));
}
private void validateBinding() {
givenTheBroker().get("/api/bindings").then()
.body("[0].source", equalTo("app_guid")).and()
.body("[0].copy", notNullValue()).and().body("$", hasSize(1));
}
private void validateProvisioning() throws JSONException,
InterruptedException, SQLException {
// Because the provision is async we have to give it some time
// to happen. We poll here and check it's state.
givenTheBroker().get("/v2/service_instances/1234").then()
.body("last_operation.state", equalTo("in progress")).and()
.statusCode(200);
waitForProvisionCompletion();
givenTheBroker().get("/api/instances").then()
.body("[0].source", equalTo(sourceInstanceId)).and()
.body("[0].copy", notNullValue()).and().body("$", hasSize(1));
givenTheBroker().get("/v2/service_instances/1234").then()
.body("last_operation.state", equalTo("succeeded")).and()
.statusCode(200);
}
private void validateSanitization(JSONObject response) throws SQLException {
// Connection conn = DriverManager.getConnection(postgresUri);
System.out.println("HELLO!!!!! " + response.toJSONString());
}
private void waitForProvisionCompletion() throws InterruptedException {
int retries = 0;
do {
ServiceInstance instance = givenTheBroker()
.get("/v2/service_instances/1234").andReturn()
.as(ServiceInstance.class);
if (!"in progress".equals(instance
.getServiceInstanceLastOperation().getState())) {
break;
}
Thread.sleep(3000);
++retries;
} while (retries != 120); // 360 seconds
}
private void waitForDeProvisionCompletion() throws InterruptedException {
int retries = 0;
int statusCode = 0;
do {
statusCode = givenTheBroker().get("/v2/service_instances/1234")
.andReturn().statusCode();
Thread.sleep(3000);
++retries;
} while (retries != 30 && 410 != statusCode); // 90 seconds or GONE
}
}