/* * Copyright (c) 2014 Intellectual Reserve, Inc. All rights reserved. * * Licensed 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 cf.spring.servicebroker; import cf.spring.servicebroker.Catalog.CatalogService; import cf.spring.servicebroker.Catalog.Plan; import cf.spring.servicebroker.ServiceBrokerHandler.ProvisionBody; import cf.spring.servicebroker.ServiceBrokerHandler.UpdateBody; import cf.spring.servicebroker.VolumeMount.DeviceType; import cf.spring.servicebroker.VolumeMount.Mode; import cf.spring.servicebroker.VolumeMount.SharedDevice; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPatch; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.RequestBuilder; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.testng.annotations.AfterClass; import org.testng.annotations.AfterMethod; import org.testng.annotations.AfterTest; import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; /** * @author Mike Heath */ public class ServiceBrokerTest extends AbstractServiceBrokerTest { private static final UUID ORG_GUID = UUID.randomUUID(); private static final UUID SPACE_GUID = UUID.randomUUID(); private static final UUID SERVICE_INSTANCE_GUID = UUID.randomUUID(); private static final UUID APPLICATION_GUID = UUID.randomUUID(); private static final String ROUTE = "www.google.com"; private static final String ROUTE_SERVICE_URL = "https://someproxy.com"; private static final String SYSLOG_DRAIN_URL = "https://somedrain.com"; private static final VolumeMount VOLUME_MOUNT = new VolumeMount("driver", "containerDir", Mode.RW, DeviceType.SHARED, new SharedDevice("volumeId", JsonNodeFactory.instance.objectNode().put("empty", true))); private static final UUID BINDING_GUID = UUID.randomUUID(); private static final Map<String, Object> PARAMETERS; private static final String PARAM_KEY = "user_provided_param"; private static final Object PARAM_VALUE = "finally"; static { PARAMETERS = new HashMap<>(); PARAMETERS.put(PARAM_KEY, PARAM_VALUE); } private static final String DASHBOARD_URL = "http:/some.url/yourservice/" + SERVICE_INSTANCE_GUID; private static final String BROKER_ID_STATIC = "some-broker-id-1"; private static final String BROKER_ID_STATIC_OTHER = "some-broker-id-1-other"; private static final String BROKER_ID_DYNAMIC = "some-broker-id-2"; private static final String PLAN_ID = "plan-id-2"; private static final String PLAN_ID_OTHER = "plan-id-2-other"; private static final String SOME_USERNAME = "some-username"; private static final String SOME_PASSWORD = "some-password"; static class Credentials { private final String username; private final String password; Credentials(String username, String password) { this.username = username; this.password = password; } public String getUsername() { return username; } public String getPassword() { return password; } } @Configuration @EnableAutoConfiguration @EnableServiceBroker(username = USERNAME, password = PASSWORD) @ServiceBroker({ @Service(id = BROKER_ID_STATIC, name="test-broker", description = "This is for testing", plans = { @ServicePlan(id = PLAN_ID, name = "test-plan", description = "Some test plan for testing.") }, requires={Permission.ROUTE_FORWARDING, Permission.SYSLOG_DRAIN, Permission.VOLUME_MOUNT}), @Service(id = BROKER_ID_STATIC_OTHER, name="test-broker-other", description = "This is for testing", plans = { @ServicePlan(id = PLAN_ID_OTHER, name = "test-plan-other", description = "Some test plan for testing.") }) }) static class ServiceBrokerConfiguration { @DynamicCatalog public Catalog getDynamicCatalog(){ final Plan plan = new Plan(PLAN_ID, "test-plan", "Some test plan for testing.", true, Collections.<String, Object>emptyMap()); final CatalogService service = new CatalogService(BROKER_ID_DYNAMIC, "test-broker-dynamic", "Dynamic service", true, Collections.<String>emptyList(), Collections.<String, Object>emptyMap(), Collections.<String>emptyList(), Collections.singletonList(plan), null); return new Catalog(Collections.singletonList(service)); } @Provision public ProvisionResponse provision(ProvisionRequest request) { assertEquals(request.getInstanceGuid(), SERVICE_INSTANCE_GUID); assertEquals(request.getPlanId(), PLAN_ID); assertEquals(request.getOrganizationGuid(), ORG_GUID); assertEquals(request.getSpaceGuid(), SPACE_GUID); assertEquals(request.getParameters().size(), 1); assertEquals(request.getParameters().get(PARAM_KEY), PARAM_VALUE); provisionCounter().incrementAndGet(); return new ProvisionResponse(DASHBOARD_URL); } @Update public void update(UpdateRequest request) { assertEquals(request.getPlanId(), PLAN_ID); assertEquals(request.getParameters().size(), 1); assertEquals(request.getParameters().get(PARAM_KEY), PARAM_VALUE); assertEquals(request.getPreviousValues().getOrganizationId(), ORG_GUID); assertEquals(request.getPreviousValues().getSpaceId(), SPACE_GUID); assertEquals(request.getPreviousValues().getPlanId(), PLAN_ID_OTHER); assertEquals(request.getPreviousValues().getServiceId(), BROKER_ID_STATIC_OTHER); updateCounter().incrementAndGet(); } @Bind public BindResponse bind(BindRequest request) { assertEquals(request.getPlanId(), PLAN_ID); BindResponse bindResponse = null; if(request.getBoundResource().getType() == BindRequest.BindingType.APPLICATION) { assertEquals(request.getApplicationGuid(), APPLICATION_GUID); assertEquals(request.getBoundResource().getResource(), APPLICATION_GUID.toString()); bindResponse = new BindResponse(new Credentials(SOME_USERNAME, SOME_PASSWORD), SYSLOG_DRAIN_URL, null, Collections.singletonList(VOLUME_MOUNT), true); } if(request.getBoundResource().getType() == BindRequest.BindingType.ROUTE) { assertEquals(request.getBoundResource().getResource(), ROUTE); bindResponse = new BindResponse(new Credentials(SOME_USERNAME, SOME_PASSWORD), null, ROUTE_SERVICE_URL, true); } assertEquals(request.getBindingGuid(), BINDING_GUID); assertEquals(request.getServiceInstanceGuid(), SERVICE_INSTANCE_GUID); bindCounter().incrementAndGet(); return bindResponse; } @Unbind public void unbind(UnbindRequest request) { assertEquals(request.getPlanId(), PLAN_ID); assertEquals(request.getServiceInstanceGuid(), SERVICE_INSTANCE_GUID); assertEquals(request.getBindingGuid(), BINDING_GUID); unbindCounter().incrementAndGet(); } @Deprovision public void deprovision(DeprovisionRequest request) { assertEquals(request.getPlanId(), PLAN_ID); assertEquals(request.getInstanceGuid(), SERVICE_INSTANCE_GUID); deprovisionCounter().incrementAndGet(); } @Bean AtomicInteger provisionCounter() { return new AtomicInteger(); } @Bean AtomicInteger updateCounter() { return new AtomicInteger(); } @Bean AtomicInteger deprovisionCounter() { return new AtomicInteger(); } @Bean AtomicInteger bindCounter() { return new AtomicInteger(); } @Bean AtomicInteger unbindCounter() { return new AtomicInteger(); } } private ConfigurableApplicationContext context; private CloseableHttpClient client; @BeforeClass public void init() { final SpringApplication application = new SpringApplication(ServiceBrokerConfiguration.class); context = application.run(); client = buildAuthenticatingClient(); } @AfterClass public void cleanup() throws Exception { context.close(); client.close(); } final String instanceUri = "http://localhost:8080/v2/service_instances/" + SERVICE_INSTANCE_GUID; final String bindingUri = "http://localhost:8080/v2/service_instances/" + SERVICE_INSTANCE_GUID + "/service_bindings/" + BINDING_GUID; @Test public void provisionStaticService() throws Exception { final ServiceBrokerHandler.ProvisionBody provisionBody = new ServiceBrokerHandler.ProvisionBody(BROKER_ID_STATIC, PLAN_ID, ORG_GUID, SPACE_GUID, PARAMETERS); doProvisionTest(provisionBody); } @Test public void provisionDynamicService() throws Exception { final ServiceBrokerHandler.ProvisionBody provisionBody = new ServiceBrokerHandler.ProvisionBody(BROKER_ID_DYNAMIC, PLAN_ID, ORG_GUID, SPACE_GUID, PARAMETERS); doProvisionTest(provisionBody); } @Test public void update() throws Exception { final AtomicInteger updateCounter = context.getBean("updateCounter", AtomicInteger.class); updateCounter.set(0); // Do update final ServiceBrokerHandler.UpdateBody updateBody = new ServiceBrokerHandler.UpdateBody(BROKER_ID_STATIC, PLAN_ID, PARAMETERS, new ServiceBrokerHandler.UpdateBody.PreviousValues(BROKER_ID_STATIC_OTHER, PLAN_ID_OTHER, ORG_GUID, SPACE_GUID)); final HttpUriRequest updateRequest = RequestBuilder.create(HttpPatch.METHOD_NAME) .setUri(instanceUri) .setEntity(new StringEntity(mapper.writeValueAsString(updateBody), ContentType.APPLICATION_JSON)) .build(); try (final CloseableHttpResponse updateResponse = client.execute(updateRequest)) { assertEquals(updateResponse.getStatusLine().getStatusCode(), 200); assertEquals(updateCounter.get(), 1); } } @Test public void bindApplication() throws Exception { final AtomicInteger bindCounter = context.getBean("bindCounter", AtomicInteger.class); bindCounter.set(0); // Do bind final ServiceBrokerHandler.BindBody bindBody = new ServiceBrokerHandler.BindBody(BROKER_ID_STATIC, PLAN_ID, APPLICATION_GUID, new ServiceBrokerHandler.BindResource(APPLICATION_GUID.toString(), null), Collections.emptyMap()); final HttpUriRequest bindRequest = RequestBuilder.put() .setUri(bindingUri) .setEntity(new StringEntity(mapper.writeValueAsString(bindBody), ContentType.APPLICATION_JSON)) .build(); try(final CloseableHttpResponse bindResponse = client.execute(bindRequest)) { assertEquals(bindResponse.getStatusLine().getStatusCode(), 201); assertEquals(bindCounter.get(), 1); final JsonNode bindResponseJson = mapper.readTree(bindResponse.getEntity().getContent()); assertTrue(bindResponseJson.has("credentials")); assertTrue(bindResponseJson.has("syslog_drain_url")); assertFalse(bindResponseJson.has("route_service_url")); assertTrue(bindResponseJson.has("volume_mounts")); JsonNode volumeMounts = bindResponseJson.get("volume_mounts"); assertTrue(volumeMounts.isArray()); assertTrue(volumeMounts.has(0)); JsonNode volumeMount = volumeMounts.get(0); assertTrue(volumeMount.has("driver")); assertTrue(volumeMount.has("container_dir")); assertTrue(volumeMount.has("device_type")); assertTrue(volumeMount.has("mode")); assertTrue(volumeMount.has("device")); JsonNode device = volumeMount.get("device"); assertTrue(device.has("volume_id")); assertTrue(device.has("mount_config")); final JsonNode credentials = bindResponseJson.get("credentials"); assertEquals(credentials.get("username").asText(), SOME_USERNAME); assertEquals(credentials.get("password").asText(), SOME_PASSWORD); } } @Test public void bindRoute() throws Exception { final AtomicInteger bindCounter = context.getBean("bindCounter", AtomicInteger.class); bindCounter.set(0); // Do bind final ServiceBrokerHandler.BindBody bindBody = new ServiceBrokerHandler.BindBody(BROKER_ID_STATIC, PLAN_ID, null, new ServiceBrokerHandler.BindResource(null, ROUTE), Collections.emptyMap()); final HttpUriRequest bindRequest = RequestBuilder.put() .setUri(bindingUri) .setEntity(new StringEntity(mapper.writeValueAsString(bindBody), ContentType.APPLICATION_JSON)) .build(); try (final CloseableHttpResponse bindResponse = client.execute(bindRequest)) { assertEquals(bindResponse.getStatusLine().getStatusCode(), 201); assertEquals(bindCounter.get(), 1); final JsonNode bindResponseJson = mapper.readTree(bindResponse.getEntity().getContent()); assertTrue(bindResponseJson.has("credentials")); assertFalse(bindResponseJson.has("syslog_drain_url")); assertFalse(bindResponseJson.has("volume_mounts")); assertEquals(bindResponseJson.get("route_service_url").asText(), ROUTE_SERVICE_URL); } } @Test public void unbind() throws Exception { final AtomicInteger unbindCounter = context.getBean("unbindCounter", AtomicInteger.class); assertEquals(unbindCounter.get(), 0); final HttpUriRequest unbindRequest = RequestBuilder.delete() .setUri(bindingUri + "?service_id=" + BROKER_ID_STATIC + "&" + "plan_id=" + PLAN_ID) .build(); try (final CloseableHttpResponse unbindResponse = client.execute(unbindRequest)) { assertEquals(unbindResponse.getStatusLine().getStatusCode(), 200); assertEquals(unbindCounter.get(), 1); } } // Do unbind @Test public void deprovision() throws Exception { final AtomicInteger deprovisionCounter = context.getBean("deprovisionCounter", AtomicInteger.class); assertEquals(deprovisionCounter.get(), 0); // Do deprovision final HttpUriRequest deprovisionRequest = RequestBuilder.delete() .setUri(instanceUri + "?service_id=" + BROKER_ID_STATIC + "&" + "plan_id=" + PLAN_ID) .build(); try (final CloseableHttpResponse deprovisionResponse = client.execute(deprovisionRequest)) { assertEquals(deprovisionResponse.getStatusLine().getStatusCode(), 200); assertEquals(deprovisionCounter.get(), 1); } } @Test public void errorWhenCallingUnknownService() throws Exception { final ServiceBrokerHandler.ProvisionBody provisionBody = new ServiceBrokerHandler.ProvisionBody("invalid-broker-id", PLAN_ID, ORG_GUID, SPACE_GUID, Collections.emptyMap()); final HttpUriRequest provisionRequest = RequestBuilder.put() .setUri(instanceUri) .setEntity(new StringEntity(mapper.writeValueAsString(provisionBody), ContentType.APPLICATION_JSON)) .build(); try (final CloseableHttpResponse provisionResponse = client.execute(provisionRequest)) { assertEquals(provisionResponse.getStatusLine().getStatusCode(), 404); final JsonNode errorJson = mapper.readTree(provisionResponse.getEntity().getContent()); assertTrue(errorJson.has("description")); } } private void doProvisionTest(ProvisionBody provisionBody) throws IOException { final AtomicInteger provisionCounter = context.getBean("provisionCounter", AtomicInteger.class); provisionCounter.set(0); // Do provision final HttpUriRequest provisionRequest = RequestBuilder.put() .setUri(instanceUri) .setEntity(new StringEntity(mapper.writeValueAsString(provisionBody), ContentType.APPLICATION_JSON)) .build(); try (final CloseableHttpResponse provisionResponse = client.execute(provisionRequest)) { assertEquals(provisionResponse.getStatusLine().getStatusCode(), 201); assertEquals(provisionCounter.get(), 1); final JsonNode provisionResponseJson = mapper.readTree(provisionResponse.getEntity().getContent()); assertTrue(provisionResponseJson.has("dashboard_url")); assertEquals(provisionResponseJson.get("dashboard_url").asText(), DASHBOARD_URL); } } }