From 329661925a2d727f9362b2d6f455e6978a14f359 Mon Sep 17 00:00:00 2001 From: Aaron Spiegel Date: Sat, 4 Nov 2017 21:28:25 -0500 Subject: [PATCH 1/6] Support host-style access for binding credentials --- README.md | 2 ++ .../broker/service/BindingWorkflow.java | 3 +- .../broker/service/BucketBindingWorkflow.java | 31 +++++++++++++++---- .../EcsServiceInstanceBindingService.java | 3 +- .../service/NamespaceBindingWorkflow.java | 2 +- .../service/RemoteConnectBindingWorkflow.java | 3 +- 6 files changed, 34 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 7f528ec..6b61f82 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,7 @@ The following feature flags are supported by the bucket & namespace. All parame | bucket binding | base-url | - | String | Base URL name for object URI | | bucket binding | use-ssl | false | Boolean | Use SSL for object endpoint | | bucket binding | permissions | - | JSON List| List of permissions for user in bucket ACL | +| bucket binding | path-style-access | true | Boolean | Use path style access for S3 URL, the alternative is to use host style access | | namespace | domain-group-admins | - | JSON List| List of domain admins to be added to namespace | | namespace | encrypted | false | Boolean | Enable encryption of namespace | | namespace | compliance-enabled | false | Boolean | Enable compliance adhearance of retention | @@ -148,6 +149,7 @@ The following feature flags are supported by the bucket & namespace. All parame | namespace | default-retention | - | Int | Number of seconds to prevent object deletion/modification | | namespace binding | base-url | - | String | Base URL name for object URI | | namespace binding | use-ssl | false | Boolean | Use SSL for object endpoint | +| namespace binding | permissions | - | JSON List| List of permissions for user in bucket ACL | \* Quotas are defined with the following format: `{quota: {limit: , warn: }}` diff --git a/src/main/java/com/emc/ecs/cloudfoundry/broker/service/BindingWorkflow.java b/src/main/java/com/emc/ecs/cloudfoundry/broker/service/BindingWorkflow.java index 930c8c2..da9f81f 100644 --- a/src/main/java/com/emc/ecs/cloudfoundry/broker/service/BindingWorkflow.java +++ b/src/main/java/com/emc/ecs/cloudfoundry/broker/service/BindingWorkflow.java @@ -16,7 +16,8 @@ public interface BindingWorkflow { void checkIfUserExists() throws EcsManagementClientException, IOException; String createBindingUser() throws EcsManagementClientException, IOException, JAXBException; void removeBinding(ServiceInstanceBinding binding) throws EcsManagementClientException, IOException, JAXBException; - Map getCredentials(String secretKey) throws IOException, EcsManagementClientException; + Map getCredentials(String secretKey, Map parameters) + throws IOException, EcsManagementClientException; ServiceInstanceBinding getBinding(Map credentials); CreateServiceInstanceAppBindingResponse getResponse(Map credentials); } \ No newline at end of file diff --git a/src/main/java/com/emc/ecs/cloudfoundry/broker/service/BucketBindingWorkflow.java b/src/main/java/com/emc/ecs/cloudfoundry/broker/service/BucketBindingWorkflow.java index 09b98c9..68c2a5b 100644 --- a/src/main/java/com/emc/ecs/cloudfoundry/broker/service/BucketBindingWorkflow.java +++ b/src/main/java/com/emc/ecs/cloudfoundry/broker/service/BucketBindingWorkflow.java @@ -96,7 +96,7 @@ public void removeBinding(ServiceInstanceBinding binding) } @Override - public Map getCredentials(String secretKey) + public Map getCredentials(String secretKey, Map parameters) throws IOException, EcsManagementClientException { ServiceInstance instance = instanceRepository.find(instanceId); if (instance == null) @@ -111,7 +111,15 @@ public Map getCredentials(String secretKey) // Add s3 URL URL baseUrl = new URL(endpoint); - credentials.put("s3Url", getS3Url(baseUrl, secretKey)); + credentials.put("s3Url", getS3Url(baseUrl, secretKey, parameters)); + + if (parameters != null && parameters.containsKey("path-style-access") && + ! (Boolean) parameters.get("path-style-access")) + { + credentials.put("path-style-access", false); + } else { + credentials.put("path-style-access", true); + } // Add bucket name from repository credentials.put("bucket", ecs.prefix(bucketName)); @@ -141,11 +149,22 @@ public CreateServiceInstanceAppBindingResponse getResponse( return resp; } - private String getS3Url(URL baseUrl, String secretKey) { + private String getS3Url(URL baseUrl, String secretKey, Map parameters) { String userInfo = getUserInfo(secretKey); - return baseUrl.getProtocol() + "://" + ecs.prefix(userInfo) + "@" + - baseUrl.getHost() + ":" + baseUrl.getPort() + "/" + - ecs.prefix(instanceId); + String s3Url = baseUrl.getProtocol() + "://" + ecs.prefix(userInfo) + "@"; + + String portString = ""; + if (baseUrl.getPort() != -1) + portString = ":" + baseUrl.getPort(); + + if (parameters != null && parameters.containsKey("path-style-access") && + ! (Boolean) parameters.get("path-style-access")) + { + s3Url = s3Url + ecs.prefix(instanceId) + "." + baseUrl.getHost() + portString; + } else { + s3Url = s3Url + baseUrl.getHost() + portString + "/" + ecs.prefix(instanceId); + } + return s3Url; } private int createUserMap() throws EcsManagementClientException { diff --git a/src/main/java/com/emc/ecs/cloudfoundry/broker/service/EcsServiceInstanceBindingService.java b/src/main/java/com/emc/ecs/cloudfoundry/broker/service/EcsServiceInstanceBindingService.java index 3108e31..aa03218 100644 --- a/src/main/java/com/emc/ecs/cloudfoundry/broker/service/EcsServiceInstanceBindingService.java +++ b/src/main/java/com/emc/ecs/cloudfoundry/broker/service/EcsServiceInstanceBindingService.java @@ -61,7 +61,8 @@ public CreateServiceInstanceBindingResponse createServiceInstanceBinding( String secretKey = workflow.createBindingUser(); LOG.info("building binding response"); - Map credentials = workflow.getCredentials(secretKey); + Map credentials = workflow.getCredentials(secretKey, + request.getParameters()); ServiceInstanceBinding binding = workflow.getBinding(credentials); LOG.info("saving binding..."); diff --git a/src/main/java/com/emc/ecs/cloudfoundry/broker/service/NamespaceBindingWorkflow.java b/src/main/java/com/emc/ecs/cloudfoundry/broker/service/NamespaceBindingWorkflow.java index 0173c2c..ca2a002 100644 --- a/src/main/java/com/emc/ecs/cloudfoundry/broker/service/NamespaceBindingWorkflow.java +++ b/src/main/java/com/emc/ecs/cloudfoundry/broker/service/NamespaceBindingWorkflow.java @@ -40,7 +40,7 @@ public void removeBinding(ServiceInstanceBinding binding) throws EcsManagementCl } @Override - public Map getCredentials(String secretKey) + public Map getCredentials(String secretKey, Map parameters) throws IOException, EcsManagementClientException { ServiceInstance instance = instanceRepository.find(instanceId); if (instance == null) diff --git a/src/main/java/com/emc/ecs/cloudfoundry/broker/service/RemoteConnectBindingWorkflow.java b/src/main/java/com/emc/ecs/cloudfoundry/broker/service/RemoteConnectBindingWorkflow.java index cc12eba..45fb9d4 100644 --- a/src/main/java/com/emc/ecs/cloudfoundry/broker/service/RemoteConnectBindingWorkflow.java +++ b/src/main/java/com/emc/ecs/cloudfoundry/broker/service/RemoteConnectBindingWorkflow.java @@ -39,7 +39,8 @@ public String createBindingUser() throws ServiceBrokerException, IOException, JA } @Override - public Map getCredentials(String secretKey) throws IOException, EcsManagementClientException { + public Map getCredentials(String secretKey, Map parameters) + throws IOException, EcsManagementClientException { Map credentials = new HashMap<>(); credentials.put("accessKey", bindingId); credentials.put("secretKey", secretKey); From c85f669bba9151842b9b092d179e807301dd3e6c Mon Sep 17 00:00:00 2001 From: Aaron Spiegel Date: Mon, 13 Nov 2017 23:50:31 -0600 Subject: [PATCH 2/6] Fix incorrect wiremock body --- .../wiremockMgmt/mappings/bucket-acl-info-testbucket4.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/resources/wiremockMgmt/mappings/bucket-acl-info-testbucket4.json b/src/test/resources/wiremockMgmt/mappings/bucket-acl-info-testbucket4.json index 8c4cf6c..855f61c 100644 --- a/src/test/resources/wiremockMgmt/mappings/bucket-acl-info-testbucket4.json +++ b/src/test/resources/wiremockMgmt/mappings/bucket-acl-info-testbucket4.json @@ -18,6 +18,6 @@ "headers": { "Content-Type": "application/xml" }, - "body": "testbucket4S3-1root2015-12-14T05:13:01.316Z-2falsetruefalsens1-1urn:storageos:ReplicationGroupInfo:2ef0a92d-cf88-4933-90ba-90245aa031b1:global" + "body": "testbucket4ns1rootfull_control" } } \ No newline at end of file From 4ffcf5918b09f43caf155445c45c84167f94b6da Mon Sep 17 00:00:00 2001 From: Aaron Spiegel Date: Mon, 13 Nov 2017 23:51:00 -0600 Subject: [PATCH 3/6] Fix volume mount error when no export-dir is defined --- .../broker/service/BucketBindingWorkflow.java | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/emc/ecs/cloudfoundry/broker/service/BucketBindingWorkflow.java b/src/main/java/com/emc/ecs/cloudfoundry/broker/service/BucketBindingWorkflow.java index 68c2a5b..78aedc5 100644 --- a/src/main/java/com/emc/ecs/cloudfoundry/broker/service/BucketBindingWorkflow.java +++ b/src/main/java/com/emc/ecs/cloudfoundry/broker/service/BucketBindingWorkflow.java @@ -43,27 +43,24 @@ public String createBindingUser() throws EcsManagementClientException, IOExcepti ServiceInstance instance = instanceRepository.find(instanceId); if (instance == null) throw new ServiceInstanceDoesNotExistException(instanceId); - String bucketName = instance.getName(); + String bucketName = instance.getName(); + String export = ""; + List permissions = null; if (parameters != null) { - - @SuppressWarnings(value = "unchecked") - List permissions = (List) parameters.get("permissions"); - if (permissions == null) { - ecs.addUserToBucket(bucketName, bindingId); - } else { - ecs.addUserToBucket(bucketName, bindingId, permissions); - } - - if (ecs.getBucketFileEnabled(bucketName)) { - String export = (String) parameters.get("export"); - if (export == null) - export = ""; - volumeMounts = createVolumeExport(export, new URL(ecs.getObjectEndpoint()), parameters); - } - - } else { + permissions = (List) parameters.get("permissions"); + export = (String) parameters.getOrDefault("export", ""); + } + + if (permissions == null) { ecs.addUserToBucket(bucketName, bindingId); + } else { + ecs.addUserToBucket(bucketName, bindingId, permissions); + } + + if (ecs.getBucketFileEnabled(bucketName)) { + volumeMounts = createVolumeExport(export, + new URL(ecs.getObjectEndpoint()), parameters); } return userSecretKey.getSecretKey(); From dcbf8b1c9a94e04c56c2242b83c442c193e26d66 Mon Sep 17 00:00:00 2001 From: Aaron Spiegel Date: Mon, 13 Nov 2017 23:51:26 -0600 Subject: [PATCH 4/6] Correct typos --- .../com/emc/ecs/cloudfoundry/broker/config/BrokerConfig.java | 1 + .../broker/service/EcsServiceInstanceBindingServiceTest.java | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/emc/ecs/cloudfoundry/broker/config/BrokerConfig.java b/src/main/java/com/emc/ecs/cloudfoundry/broker/config/BrokerConfig.java index a42e0e5..dcc4097 100644 --- a/src/main/java/com/emc/ecs/cloudfoundry/broker/config/BrokerConfig.java +++ b/src/main/java/com/emc/ecs/cloudfoundry/broker/config/BrokerConfig.java @@ -162,6 +162,7 @@ public String getObjectEndpoint() { public void setObjectEndpoint(String objectEndpoint) { this.objectEndpoint = objectEndpoint; } + public String getNfsMountHost() { return nfsMountHost; } diff --git a/src/test/java/com/emc/ecs/cloudfoundry/broker/service/EcsServiceInstanceBindingServiceTest.java b/src/test/java/com/emc/ecs/cloudfoundry/broker/service/EcsServiceInstanceBindingServiceTest.java index b5096ef..7458d8a 100644 --- a/src/test/java/com/emc/ecs/cloudfoundry/broker/service/EcsServiceInstanceBindingServiceTest.java +++ b/src/test/java/com/emc/ecs/cloudfoundry/broker/service/EcsServiceInstanceBindingServiceTest.java @@ -22,7 +22,6 @@ import org.springframework.cloud.servicebroker.model.VolumeMount; import javax.xml.bind.JAXBException; -import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; @@ -343,7 +342,7 @@ public void testNamespaceCreateConnectRemoteService() throws IOException, JAXBEx ArgumentCaptor bindingCaptor = ArgumentCaptor.forClass(ServiceInstanceBinding.class); ArgumentCaptor instanceCaptor = - ArgumentCaptor .forClass(ServiceInstance.class); + ArgumentCaptor.forClass(ServiceInstance.class); when(instanceRepository.find(SERVICE_INSTANCE_ID)).thenReturn(serviceInstanceFixture()); doNothing().when(instanceRepository).save(instanceCaptor.capture()); doNothing().when(repository).save(bindingCaptor.capture()); From 55cf415a9476c4bf2445b69cf23be0c759039c37 Mon Sep 17 00:00:00 2001 From: Aaron Spiegel Date: Mon, 13 Nov 2017 23:51:49 -0600 Subject: [PATCH 5/6] Add new tests for bucket-binding-workflow --- build.gradle | 3 +- src/test/java/com/emc/ecs/TestSuite.java | 4 +- .../service/BucketBindingWorkflowTest.java | 337 ++++++++++++++++++ .../java/com/emc/ecs/common/Fixtures.java | 39 +- 4 files changed, 377 insertions(+), 6 deletions(-) create mode 100644 src/test/java/com/emc/ecs/cloudfoundry/broker/service/BucketBindingWorkflowTest.java diff --git a/build.gradle b/build.gradle index d6d9049..617050a 100644 --- a/build.gradle +++ b/build.gradle @@ -53,7 +53,8 @@ dependencies { testCompile(group: 'com.github.tomakehurst', name: 'wiremock-standalone', version: '2.5.1') testCompile(group: 'org.powermock', name: 'powermock-api-mockito', version: '1.7.1') testCompile(group: 'org.powermock', name: 'powermock-module-junit4', version: '1.7.1') - + testCompile(group: 'com.github.paulcwarren', name: 'ginkgo4j', version: '1.0.7') + } diff --git a/src/test/java/com/emc/ecs/TestSuite.java b/src/test/java/com/emc/ecs/TestSuite.java index 6e826c0..22c7f9b 100644 --- a/src/test/java/com/emc/ecs/TestSuite.java +++ b/src/test/java/com/emc/ecs/TestSuite.java @@ -4,6 +4,7 @@ import com.emc.ecs.cloudfoundry.broker.model.ServiceDefinitionProxyTest; import com.emc.ecs.cloudfoundry.broker.repository.ServiceInstanceBindingRepositoryTest; import com.emc.ecs.cloudfoundry.broker.repository.ServiceInstanceRepositoryTest; +import com.emc.ecs.cloudfoundry.broker.service.BucketBindingWorkflowTest; import com.emc.ecs.cloudfoundry.broker.service.EcsServiceInstanceBindingServiceTest; import com.emc.ecs.cloudfoundry.broker.service.EcsServiceInstanceServiceTest; import com.emc.ecs.cloudfoundry.broker.service.EcsServiceTest; @@ -38,7 +39,8 @@ ServiceInstanceBindingRepositoryTest.class, ServiceInstanceRepositoryTest.class, EcsServiceInstanceBindingServiceTest.class, - EcsServiceInstanceServiceTest.class + EcsServiceInstanceServiceTest.class, + BucketBindingWorkflowTest.class }) public class TestSuite { diff --git a/src/test/java/com/emc/ecs/cloudfoundry/broker/service/BucketBindingWorkflowTest.java b/src/test/java/com/emc/ecs/cloudfoundry/broker/service/BucketBindingWorkflowTest.java new file mode 100644 index 0000000..600c9b7 --- /dev/null +++ b/src/test/java/com/emc/ecs/cloudfoundry/broker/service/BucketBindingWorkflowTest.java @@ -0,0 +1,337 @@ +package com.emc.ecs.cloudfoundry.broker.service; + +import com.emc.ecs.cloudfoundry.broker.repository.ServiceInstanceBinding; +import com.emc.ecs.cloudfoundry.broker.repository.ServiceInstanceRepository; +import com.emc.ecs.management.sdk.model.UserSecretKey; +import com.github.paulcwarren.ginkgo4j.Ginkgo4jRunner; +import org.assertj.core.util.Lists; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.stubbing.Answer; +import org.springframework.cloud.servicebroker.exception.ServiceInstanceBindingExistsException; +import org.springframework.cloud.servicebroker.exception.ServiceInstanceDoesNotExistException; +import org.springframework.cloud.servicebroker.model.CreateServiceInstanceAppBindingResponse; +import org.springframework.cloud.servicebroker.model.CreateServiceInstanceBindingRequest; +import org.springframework.cloud.servicebroker.model.VolumeMount; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.emc.ecs.common.Fixtures.*; +import static com.github.paulcwarren.ginkgo4j.Ginkgo4jDSL.*; +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.*; + +@RunWith(Ginkgo4jRunner.class) +public class BucketBindingWorkflowTest { + + private EcsService ecs; + private ServiceInstanceRepository instanceRepo; + private Map parameters = new HashMap<>(); + private Map credentials = new HashMap<>(); + @SuppressWarnings("unchecked") + private Class> listClass = (Class>) (Class) ArrayList.class; + private ArgumentCaptor> permsCaptor = ArgumentCaptor.forClass(listClass); + private ArgumentCaptor pathCaptor = ArgumentCaptor.forClass(String.class); + private BindingWorkflow workflow; + + { + Describe("BucketBindingWorkflow", () -> { + BeforeEach(() -> { + ecs = mock(EcsService.class); + instanceRepo = mock(ServiceInstanceRepository.class); + workflow = new BucketBindingWorkflow(instanceRepo, ecs); + }); + + Context("with binding ID conflict", () -> { + BeforeEach(() -> + when(ecs.userExists(eq(BINDING_ID))).thenReturn(true)); + + It("should throw an binding-exists exception", () -> { + try { + workflow.checkIfUserExists(); + } catch (ServiceInstanceBindingExistsException e) { + assert e.getClass().equals(ServiceInstanceBindingExistsException.class); + } + }); + }); + + Context("without binding ID conflict", () -> { + BeforeEach(() -> { + CreateServiceInstanceBindingRequest req = bucketBindingRequestFixture(); + workflow = workflow.withCreateRequest(req); + when(ecs.userExists(eq(BINDING_ID))).thenReturn(false); + }); + + Context("when the service instance doesn't exist", () -> { + BeforeEach(() -> + when(instanceRepo.find(eq(SERVICE_INSTANCE_ID))).thenReturn(null)); + + It("should throw a service-does-not-exist exception on createBindingUser", () -> { + try { + workflow.createBindingUser(); + } catch (ServiceInstanceDoesNotExistException e) { + assert e.getClass().equals(ServiceInstanceDoesNotExistException.class); + } + }); + + It("should throw a service-does-not-exist exception on removeBinding", () -> { + try { + workflow.removeBinding(bindingInstanceFixture()); + } catch (ServiceInstanceDoesNotExistException e) { + assert e.getClass().equals(ServiceInstanceDoesNotExistException.class); + } + }); + + It("should throw a service-does-not-exist exception on getCredentials", () -> { + try { + workflow.getCredentials(SECRET_KEY, new HashMap<>()); + } catch (ServiceInstanceDoesNotExistException e) { + assert e.getClass().equals(ServiceInstanceDoesNotExistException.class); + } + }); + + }); + + + Context("when the service instance exists", () -> { + BeforeEach(() -> { + // Mock out all prefix calls to return prefixed argument + when(ecs.prefix(anyString())).thenAnswer((Answer) invocation -> { + Object[] args = invocation.getArguments(); + return (String) args[0]; + }); + + // Return a default endpoint + when(ecs.getObjectEndpoint()).thenReturn(OBJ_ENDPOINT); + + // Mock service instance repo lookups + when(instanceRepo.find(eq(SERVICE_INSTANCE_ID))).thenReturn(serviceInstanceFixture()); + + UserSecretKey userSecretKey = new UserSecretKey(); + userSecretKey.setSecretKey(SECRET_KEY); + when(ecs.createUser(eq(BINDING_ID))).thenReturn(userSecretKey); + + // Create credentials fixture + String s3Url = "http://" + BINDING_ID + ":" + SECRET_KEY + + "@127.0.0.1:9020/" + SERVICE_INSTANCE_ID; + credentials.put("accessKey", BINDING_ID); + credentials.put("secretKey", SECRET_KEY); + credentials.put("endpoint", OBJ_ENDPOINT); + credentials.put("bucket", SERVICE_INSTANCE_ID); + credentials.put("s3Url", s3Url); + credentials.put("path-style-access", true); + }); + + Context("basic bucket binding", () -> { + + BeforeEach(() -> + doNothing().when(ecs).addUserToBucket(eq(SERVICE_INSTANCE_ID), + eq(BINDING_ID))); + + It("should create a new user", () -> { + workflow.createBindingUser(); + verify(ecs, times(1)) + .createUser(BINDING_ID); + }); + + It("should add the user to a bucket", () -> { + workflow.createBindingUser(); + verify(ecs, times(1)) + .addUserToBucket(eq(SERVICE_INSTANCE_ID), eq(BINDING_ID)); + }); + + It("should delete the user", () -> { + workflow.removeBinding(bindingInstanceFixture()); + verify(ecs, times(1)) + .deleteUser(BINDING_ID); + }); + + It("should remove the user from a bucket", () -> { + workflow.removeBinding(bindingInstanceFixture()); + verify(ecs, times(1)) + .removeUserFromBucket(SERVICE_INSTANCE_ID, BINDING_ID); + }); + + + Context("with a port in the object endpoint", () -> + It("should return credentials", () -> { + Map actual = + workflow.getCredentials(SECRET_KEY, parameters); + assertCredentialsEqual(actual, credentials); + })); + + Context("with no port in the object endpoint", () -> { + BeforeEach(() -> { + when(ecs.getObjectEndpoint()).thenReturn("http://127.0.0.1"); + String s3Url = "http://" + BINDING_ID + ":" + SECRET_KEY + "@127.0.0.1/" + + SERVICE_INSTANCE_ID; + credentials.put("s3Url", s3Url); + credentials.put("endpoint", "http://127.0.0.1"); + }); + + It("should return credentials", () -> { + Map actual = + workflow.getCredentials(SECRET_KEY, parameters); + assertCredentialsEqual(actual, credentials); + }); + + }); + + It("should return a binding", () -> { + ServiceInstanceBinding binding = workflow.getBinding(credentials); + assertEquals(binding.getBindingId(), BINDING_ID); + assertCredentialsEqual(binding.getCredentials(), credentials); + assertEquals(binding.getServiceDefinitionId(), BUCKET_SERVICE_ID); + assertEquals(binding.getPlanId(), BUCKET_PLAN_ID1); + }); + + It("should return a response", () -> { + CreateServiceInstanceAppBindingResponse resp = + workflow.getResponse(credentials); + assertCredentialsEqual(resp.getCredentials(), credentials); + }); + }); + + Context("permissions bucket binding", () -> { + BeforeEach(() -> { + // Add params to workflow + List readOnlyPerms = + Lists.newArrayList("READ", "READ_ACL"); + parameters.put("permissions", readOnlyPerms); + + CreateServiceInstanceBindingRequest req = bucketBindingRequestFixture(parameters); + workflow = workflow.withCreateRequest(req); + + doNothing().when(ecs).addUserToBucket(eq(SERVICE_INSTANCE_ID), + eq(BINDING_ID), any(listClass)); + }); + + It("should add the user to the bucket with ACL", () -> { + workflow.createBindingUser(); + verify(ecs, times(1)) + .addUserToBucket(eq(SERVICE_INSTANCE_ID), eq(BINDING_ID), + permsCaptor.capture()); + List perms = permsCaptor.getValue(); + assertEquals(2, perms.size()); + assertEquals("READ", perms.get(0)); + assertEquals("READ_ACL", perms.get(1)); + }); + }); + + Context("without path style access in binding", () -> { + BeforeEach(() -> { + parameters.put("path-style-access", false); + + CreateServiceInstanceBindingRequest req = bucketBindingRequestFixture(parameters); + workflow = workflow.withCreateRequest(req); + + String s3Url = "http://" + BINDING_ID + ":" + SECRET_KEY + "@" + + SERVICE_INSTANCE_ID + ".127.0.0.1:9020"; + credentials.put("s3Url", s3Url); + credentials.put("path-style-access", false); + }); + + It("should return credentials with host-style access", () -> { + Map actual = workflow.getCredentials(SECRET_KEY, parameters); + assertCredentialsEqual(actual, credentials); + }); + }); + + Context("with volume mount", () -> { + BeforeEach(() -> + when(ecs.getBucketFileEnabled(eq(SERVICE_INSTANCE_ID))) + .thenReturn(true)); + + Context("at bucket root", () -> { + BeforeEach(() -> { + String path = "/ns1/" + SERVICE_INSTANCE_ID + "/"; + when(ecs.addExportToBucket(eq(SERVICE_INSTANCE_ID), any(String.class))) + .thenReturn(path); + doNothing().when(ecs).deleteUserMap(eq(BINDING_ID), any(String.class)); + }); + + It("should create an NFS export", () -> { + workflow.createBindingUser(); + + verify(ecs, times(1)) + .getBucketFileEnabled(eq(SERVICE_INSTANCE_ID)); + verify(ecs, times(1)) + .createUser(eq(BINDING_ID)); + verify(ecs, times(1)) + .addExportToBucket(eq(SERVICE_INSTANCE_ID), pathCaptor.capture()); + assertEquals(pathCaptor.getValue(), ""); + }); + + It("should delete the NFS export", () -> { + workflow.removeBinding(bindingInstanceVolumeMountFixture()); + verify(ecs, times(1)) + .deleteUser(BINDING_ID); + verify(ecs, times(1)) + .deleteUserMap(eq(BINDING_ID), eq("456")); + }); + + It("should return a binding mount", () -> { + workflow.createBindingUser(); + ServiceInstanceBinding binding = workflow.getBinding(credentials); + + assertEquals(binding.getBindingId(), BINDING_ID); + + List mounts = binding.getVolumeMounts(); + assertEquals(1, mounts.size()); + + String containerDir = "/var/vcap/data/" + BINDING_ID; + assertEquals(containerDir, mounts.get(0).getContainerDir()); + }); + + It("should return a response with mount", () -> { + workflow.createBindingUser(); + CreateServiceInstanceAppBindingResponse resp = + workflow.getResponse(credentials); + + List mounts = resp.getVolumeMounts(); + assertEquals(1, mounts.size()); + + String containerDir = "/var/vcap/data/" + BINDING_ID; + assertEquals(containerDir, mounts.get(0).getContainerDir()); + }); + }); + + Context("at nested path", () -> { + BeforeEach(() -> { + parameters.put("export", "some-path"); + workflow = workflow.withCreateRequest(bucketBindingRequestFixture(parameters)); + String path = "/ns1/" + SERVICE_INSTANCE_ID + "/some-path"; + when(ecs.addExportToBucket(eq(SERVICE_INSTANCE_ID), any(String.class))) + .thenReturn(path); + }); + + It("should create an NFS export", () -> { + workflow.createBindingUser(); + + verify(ecs, times(1)) + .createUser(eq(BINDING_ID)); + verify(ecs, times(1)) + .addExportToBucket(eq(SERVICE_INSTANCE_ID), pathCaptor.capture()); + assertEquals(pathCaptor.getValue(), "some-path"); + }); + }); + + }); + }); + }); + }); + } + + private static void assertCredentialsEqual(Map actual, Map credentials) { + assertEquals(credentials.get("accessKey"), actual.get("accessKey")); + assertEquals(credentials.get("secretKey"), actual.get("secretKey")); + assertEquals(credentials.get("endpoint"), actual.get("endpoint")); + assertEquals(credentials.get("bucket"), actual.get("bucket")); + assertEquals(credentials.get("s3Url"), actual.get("s3Url")); + assertEquals(credentials.get("path-style-access"), actual.get("path-style-access")); + } +} diff --git a/src/test/java/com/emc/ecs/common/Fixtures.java b/src/test/java/com/emc/ecs/common/Fixtures.java index 2c3ead2..4c81291 100644 --- a/src/test/java/com/emc/ecs/common/Fixtures.java +++ b/src/test/java/com/emc/ecs/common/Fixtures.java @@ -10,10 +10,7 @@ import org.springframework.cloud.servicebroker.model.fixture.ServiceInstanceBindingFixture; import org.springframework.cloud.servicebroker.model.fixture.ServiceInstanceFixture; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; public class Fixtures { private static final String QUOTA = "quota"; @@ -72,6 +69,7 @@ public class Fixtures { public static final String REMOTE_CONNECT_KEY = "95cb87f5-80d3-48b7-b860-072aeae4a918"; public static final String EXPORT_NAME = "/export/dir"; public static final String VOLUME_MOUNT = "/mount/dir"; + public static final String SECRET_KEY = "6b056992-a14a-4fd1-a642-f44a821a7755"; public static ServiceDefinitionProxy bucketServiceFixture() { /* @@ -260,6 +258,15 @@ public static CreateServiceInstanceBindingRequest bucketBindingPermissionRequest .withServiceInstanceId(SERVICE_INSTANCE_ID); } + public static CreateServiceInstanceBindingRequest bucketBindingRequestFixture(Map parameters) { + Map bindResource = new HashMap<>(); + bindResource.put("app_guid", APP_GUID); + return new CreateServiceInstanceBindingRequest(BUCKET_SERVICE_ID, + BUCKET_PLAN_ID1, APP_GUID, bindResource, parameters) + .withBindingId(BINDING_ID) + .withServiceInstanceId(SERVICE_INSTANCE_ID); + } + public static CreateServiceInstanceBindingRequest bucketBindingExportRequestFixture() { Map bindResource = new HashMap<>(); bindResource.put("app_guid", APP_GUID); @@ -301,6 +308,30 @@ public static ServiceInstanceBinding bindingInstanceFixture() return binding; } + public static ServiceInstanceBinding bindingInstanceVolumeMountFixture() + throws EcsManagementClientException, + EcsManagementResourceNotFoundException { + Map creds = new HashMap<>(); + creds.put("accessKey", "user"); + creds.put("bucket", "bucket"); + creds.put("secretKey", "password"); + creds.put("endpoint", OBJ_ENDPOINT); + ServiceInstanceBinding binding = new ServiceInstanceBinding( + ServiceInstanceBindingFixture.buildCreateAppBindingRequest()); + binding.setBindingId("service-inst-bind-one-id"); + binding.setCredentials(creds); + Map opts = new HashMap<>(); + opts.put("source", "nfs://127.0.0.1/ns1/service-inst-id/"); + opts.put("uid", "456"); + List mounts = Arrays.asList( + new VolumeMount("nfsv3driver", "/var/vcap/data/" + BINDING_ID, + VolumeMount.Mode.READ_WRITE, VolumeMount.DeviceType.SHARED, + new SharedVolumeDevice("123", opts)) + ); + binding.setVolumeMounts(mounts); + return binding; + } + public static ServiceInstanceBinding bindingRemoteAccessFixture() throws EcsManagementClientException, EcsManagementResourceNotFoundException { From 9f994d29eafa1a9708ffa948e839e84fcece4168 Mon Sep 17 00:00:00 2001 From: Aaron Spiegel Date: Mon, 13 Nov 2017 23:57:42 -0600 Subject: [PATCH 6/6] Fix readme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Permissions don’t do anything for namespaces… --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 6b61f82..b5f72c3 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,6 @@ The following feature flags are supported by the bucket & namespace. All parame | namespace | default-retention | - | Int | Number of seconds to prevent object deletion/modification | | namespace binding | base-url | - | String | Base URL name for object URI | | namespace binding | use-ssl | false | Boolean | Use SSL for object endpoint | -| namespace binding | permissions | - | JSON List| List of permissions for user in bucket ACL | \* Quotas are defined with the following format: `{quota: {limit: , warn: }}`