From: pkaras Date: Fri, 31 May 2019 08:17:40 +0000 (+0200) Subject: AafTopicSetupService implementation X-Git-Tag: 2.0.1~16 X-Git-Url: https://gerrit.onap.org/r/gitweb?p=dmaap%2Fdbcapi.git;a=commitdiff_plain;h=5a9cbe16fa36ba35dc9ba104a287977e975a455b AafTopicSetupService implementation Change-Id: I2dc702cbe1e2e6fedc02b78468f57fa58e994ac2 Issue-ID: DMAAP-1211 Signed-off-by: piotr.karas --- diff --git a/src/main/java/org/onap/dmaap/dbcapi/aaf/AafNamespace.java b/src/main/java/org/onap/dmaap/dbcapi/aaf/AafNamespace.java index 793260c..b5a887c 100644 --- a/src/main/java/org/onap/dmaap/dbcapi/aaf/AafNamespace.java +++ b/src/main/java/org/onap/dmaap/dbcapi/aaf/AafNamespace.java @@ -8,9 +8,9 @@ * 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. @@ -21,30 +21,31 @@ package org.onap.dmaap.dbcapi.aaf; -import java.util.ArrayList; - import org.apache.log4j.Logger; import org.onap.dmaap.dbcapi.util.DmaapConfig; +import java.util.ArrayList; +import java.util.Objects; + public class AafNamespace extends AafObject { private static final Logger logger = Logger.getLogger(AafNamespace.class); - + private String name; private ArrayList admin; private ArrayList responsible; - + // in some environments, an AAF Namespace must be owned by a human. // So, when needed, this var can be set via a property private static String NsOwnerIdentity; - + public AafNamespace(String ns, String identity ) { super(); DmaapConfig p = (DmaapConfig)DmaapConfig.getConfig(); NsOwnerIdentity = p.getProperty( "aaf.NsOwnerIdentity", ""); this.admin = new ArrayList<>(); this.responsible = new ArrayList<>(); - + this.name = ns; this.admin.add( identity ); this.responsible.add( NsOwnerIdentity ); @@ -87,7 +88,7 @@ public class AafNamespace extends AafObject { public String toJSON() { - String postJSON = String.format(" { \"name\": \"%s\", \"admin\": [", + String postJSON = String.format(" { \"name\": \"%s\", \"admin\": [", this.getName() ); postJSON += separatedList( this.getAdmin(), "," ); @@ -95,11 +96,22 @@ public class AafNamespace extends AafObject { postJSON += separatedList( this.getResponsible(), ","); postJSON += "]}"; logger.info( "returning JSON: " + postJSON); - + return postJSON; } - - - - + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AafNamespace that = (AafNamespace) o; + return Objects.equals(name, that.name) && + Objects.equals(admin, that.admin) && + Objects.equals(responsible, that.responsible); + } + + @Override + public int hashCode() { + return Objects.hash(name, admin, responsible); + } } diff --git a/src/main/java/org/onap/dmaap/dbcapi/aaf/AafRole.java b/src/main/java/org/onap/dmaap/dbcapi/aaf/AafRole.java index 6acbefd..34a6110 100644 --- a/src/main/java/org/onap/dmaap/dbcapi/aaf/AafRole.java +++ b/src/main/java/org/onap/dmaap/dbcapi/aaf/AafRole.java @@ -22,6 +22,8 @@ package org.onap.dmaap.dbcapi.aaf; import org.apache.log4j.Logger; +import java.util.Objects; + public class AafRole extends AafObject { static final Logger logger = Logger.getLogger(AafRole.class); @@ -59,8 +61,18 @@ public class AafRole extends AafObject { return postJSON; } - - - - + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AafRole aafRole = (AafRole) o; + return Objects.equals(namespace, aafRole.namespace) && + Objects.equals(role, aafRole.role); + } + + @Override + public int hashCode() { + return Objects.hash(namespace, role); + } } diff --git a/src/main/java/org/onap/dmaap/dbcapi/service/AafTopicSetupService.java b/src/main/java/org/onap/dmaap/dbcapi/service/AafTopicSetupService.java new file mode 100644 index 0000000..031f594 --- /dev/null +++ b/src/main/java/org/onap/dmaap/dbcapi/service/AafTopicSetupService.java @@ -0,0 +1,164 @@ +/*- + * ============LICENSE_START======================================================= + * org.onap.dmaap + * ================================================================================ + * Copyright (C) 2019 Nokia Intellectual Property. 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. + * ============LICENSE_END========================================================= + */ +package org.onap.dmaap.dbcapi.service; + +import org.onap.dmaap.dbcapi.aaf.AafNamespace; +import org.onap.dmaap.dbcapi.aaf.AafRole; +import org.onap.dmaap.dbcapi.aaf.AafService; +import org.onap.dmaap.dbcapi.aaf.DmaapGrant; +import org.onap.dmaap.dbcapi.aaf.DmaapPerm; +import org.onap.dmaap.dbcapi.logging.BaseLoggingClass; +import org.onap.dmaap.dbcapi.model.ApiError; +import org.onap.dmaap.dbcapi.model.Topic; + +import static java.lang.String.format; + +class AafTopicSetupService extends BaseLoggingClass { + + private final AafService aafService; + private final DmaapService dmaapService; + private final boolean createTopicRoles; + + AafTopicSetupService(AafService aafService, DmaapService dmaapService, boolean createTopicRoles) { + this.aafService = aafService; + this.dmaapService = dmaapService; + this.createTopicRoles = createTopicRoles; + } + + ApiError aafTopicSetup(Topic topic) { + + try { + String instance = ":topic." + topic.getFqtn(); + String topicPerm = dmaapService.getTopicPerm(); + DmaapPerm pubPerm = createPermission(topicPerm, instance, "pub"); + DmaapPerm subPerm = createPermission(topicPerm, instance, "sub"); + DmaapPerm viewPerm = createPermission(topicPerm, instance, "view"); + + // creating Topic Roles was not an original feature. + // For backwards compatibility, only do this if the feature is enabled. + // Also, if the namespace of the topic is a foreign namespace, (i.e. not the same as our root ns) + // then we likely don't have permission to create sub-ns and Roles so don't try. + if (createTopicRoles && topic.getFqtn().startsWith(getTopicsNsRoot())) { + createNamespace(topic); + + AafRole pubRole = createRole(topic, "publisher"); + topic.setPublisherRole(pubRole.getFullyQualifiedRole()); + + AafRole subRole = createRole(topic, "subscriber"); + topic.setSubscriberRole(subRole.getFullyQualifiedRole()); + + grantPermToRole(pubRole, pubPerm); + grantPermToRole(pubRole, viewPerm); + + grantPermToRole(subRole, subPerm); + grantPermToRole(subRole, viewPerm); + } + + } catch (TopicSetupException ex) { + return new ApiError(ex.getCode(), ex.getMessage(), ex.getFields()); + } + return okStatus(); + } + + private String getTopicsNsRoot() throws TopicSetupException { + String nsr = dmaapService.getDmaap().getTopicNsRoot(); + if (nsr == null) { + throw new TopicSetupException(500, + "Unable to establish AAF namespace root: (check /dmaap object)", "topicNsRoot"); + } + return nsr; + } + + private DmaapPerm createPermission(String permission, String instance, String action) throws TopicSetupException { + DmaapPerm perm = new DmaapPerm(permission, instance, action); + int rc = aafService.addPerm(perm); + if (rc != 201 && rc != 409) { + throw new TopicSetupException(500, + format("Unexpected response from AAF: %d permission=%s instance=%s action=%s", + rc, perm, instance, action)); + } + return perm; + } + + private void grantPermToRole(AafRole aafRole, DmaapPerm perm) throws TopicSetupException { + DmaapGrant g = new DmaapGrant(perm, aafRole.getFullyQualifiedRole()); + int rc = aafService.addGrant(g); + if (rc != 201 && rc != 409) { + String message = format("Grant of %s failed for %s", perm.toString(), aafRole.getFullyQualifiedRole()); + logger.warn(message); + throw new TopicSetupException(rc, message); + } + } + + private void createNamespace(Topic topic) throws TopicSetupException { + AafNamespace ns = new AafNamespace(topic.getFqtn(), aafService.getIdentity()); + int rc = aafService.addNamespace(ns); + if (rc != 201 && rc != 409) { + throw new TopicSetupException(500, + format("Unexpected response from AAF: %d namespace=%s identity=%s", + rc, topic.getFqtn(), aafService.getIdentity())); + } + } + + private AafRole createRole(Topic topic, String roleName) throws TopicSetupException { + int rc; + AafRole role = new AafRole(topic.getFqtn(), roleName); + rc = aafService.addRole(role); + if (rc != 201 && rc != 409) { + throw new TopicSetupException(500, + format("Unexpected response from AAF: %d topic=%s role=%s", + rc, topic.getFqtn(), roleName)); + } + return role; + } + + private ApiError okStatus() { + return new ApiError(200, "OK"); + } + + private class TopicSetupException extends Exception { + private final int code; + private final String message; + private final String fields; + + TopicSetupException(int code, String message) { + this(code, message, ""); + } + + TopicSetupException(int code, String message, String fields) { + this.code = code; + this.message = message; + this.fields = fields; + } + + public int getCode() { + return code; + } + + @Override + public String getMessage() { + return message; + } + + public String getFields() { + return fields; + } + } +} diff --git a/src/test/java/org/onap/dmaap/dbcapi/service/AafTopicSetupServiceTest.java b/src/test/java/org/onap/dmaap/dbcapi/service/AafTopicSetupServiceTest.java new file mode 100644 index 0000000..a250c90 --- /dev/null +++ b/src/test/java/org/onap/dmaap/dbcapi/service/AafTopicSetupServiceTest.java @@ -0,0 +1,338 @@ +/*- + * ============LICENSE_START======================================================= + * org.onap.dmaap + * ================================================================================ + * Copyright (C) 2019 Nokia Intellectual Property. 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. + * ============LICENSE_END========================================================= + */ +package org.onap.dmaap.dbcapi.service; + +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.onap.dmaap.dbcapi.aaf.AafNamespace; +import org.onap.dmaap.dbcapi.aaf.AafRole; +import org.onap.dmaap.dbcapi.aaf.AafService; +import org.onap.dmaap.dbcapi.aaf.AafUserRole; +import org.onap.dmaap.dbcapi.aaf.DmaapGrant; +import org.onap.dmaap.dbcapi.aaf.DmaapPerm; +import org.onap.dmaap.dbcapi.model.ApiError; +import org.onap.dmaap.dbcapi.model.Dmaap; +import org.onap.dmaap.dbcapi.model.Topic; + +import java.util.List; + +import static com.google.common.collect.Lists.newArrayList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.BDDMockito.given; + +@RunWith(JUnitParamsRunner.class) +public class AafTopicSetupServiceTest { + + private static final int INTERNAL_SERVER_ERROR = 500; + private static final int NOT_FOUND = 404; + private static final int CREATED = 201; + private static final String TOPIC_NS_ROOT = "org.onap.dmaap.mr"; + private static final String TOPIC_PERM = "org.onap.dmaap.mr.topic"; + private static final String TOPIC_FQTN = "org.onap.dmaap.mr.sample_topic"; + private static final String IDENTITY = "dmaap-bc@dmaap-bc.onap.org"; + private AafServiceStub aafService = new AafServiceStub(); + @Mock + private DmaapService dmaapService; + private AafTopicSetupService aafTopicSetupService; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + Dmaap dmaap = new Dmaap(); + dmaap.setTopicNsRoot(TOPIC_NS_ROOT); + given(dmaapService.getDmaap()).willReturn(dmaap); + given(dmaapService.getTopicPerm()).willReturn(TOPIC_PERM); + aafTopicSetupService = new AafTopicSetupService(aafService, dmaapService, true); + } + + @Test + @Parameters({"201", "409"}) + public void shouldCreatePublisherSubscriberViewerPermissions(int aafServiceReturnedCode) { + aafService.givenReturnCode(aafServiceReturnedCode); + + aafTopicSetupService.aafTopicSetup(givenTopic(TOPIC_FQTN)); + + aafService.shouldAddPerm(new DmaapPerm(TOPIC_PERM, ":topic." + TOPIC_FQTN, "pub")); + aafService.shouldAddPerm(new DmaapPerm(TOPIC_PERM, ":topic." + TOPIC_FQTN, "sub")); + aafService.shouldAddPerm(new DmaapPerm(TOPIC_PERM, ":topic." + TOPIC_FQTN, "view")); + } + + @Test + public void shouldReturnOkStatusWhenNoError() { + aafService.givenReturnCode(201); + + ApiError apiError = aafTopicSetupService.aafTopicSetup(givenTopic(TOPIC_FQTN)); + + assertOkStatus(apiError); + } + + @Test + @Parameters({"201", "409"}) + public void shouldAddNamespace(int aafServiceReturnedCode) { + aafService.givenReturnCode(aafServiceReturnedCode); + Topic topic = givenTopic(TOPIC_FQTN); + + aafTopicSetupService.aafTopicSetup(topic); + + AafNamespace namespace = new AafNamespace(TOPIC_FQTN, IDENTITY); + aafService.shouldAddNamespace(namespace); + } + + @Test + @Parameters({"201", "409"}) + public void shouldCretePublisherRoleAndSetItToTopic(int aafServiceReturnedCode) { + aafService.givenReturnCode(aafServiceReturnedCode); + Topic topic = givenTopic(TOPIC_FQTN); + + aafTopicSetupService.aafTopicSetup(topic); + + AafRole role = new AafRole(TOPIC_FQTN, "publisher"); + aafService.shouldAddRole(role); + assertEquals(role.getFullyQualifiedRole(), topic.getPublisherRole()); + } + + @Test + @Parameters({"201", "409"}) + public void shouldCreteSubscriberRoleAndSetItToTopic(int aafServiceReturnedCode) { + aafService.givenReturnCode(aafServiceReturnedCode); + Topic topic = givenTopic(TOPIC_FQTN); + + aafTopicSetupService.aafTopicSetup(topic); + + AafRole role = new AafRole(TOPIC_FQTN, "subscriber"); + aafService.shouldAddRole(role); + assertEquals(role.getFullyQualifiedRole(), topic.getSubscriberRole()); + } + + @Test + @Parameters({"201", "409"}) + public void shouldGrantPubAndViewPermissionToPublisherRole(int aafServiceReturnedCode) { + aafService.givenReturnCode(aafServiceReturnedCode); + + aafTopicSetupService.aafTopicSetup(givenTopic(TOPIC_FQTN)); + + AafRole role = new AafRole(TOPIC_FQTN, "publisher"); + DmaapPerm pubPerm = new DmaapPerm(TOPIC_PERM, ":topic." + TOPIC_FQTN, "pub"); + DmaapPerm viewPerm = new DmaapPerm(TOPIC_PERM, ":topic." + TOPIC_FQTN, "view"); + aafService.shouldAddGrant(new DmaapGrant(pubPerm, role.getFullyQualifiedRole())); + aafService.shouldAddGrant(new DmaapGrant(viewPerm, role.getFullyQualifiedRole())); + } + + @Test + @Parameters({"201", "409"}) + public void shouldGrantSubAndViewPermissionToSubscriberRole(int aafServiceReturnedCode) { + aafService.givenReturnCode(aafServiceReturnedCode); + + aafTopicSetupService.aafTopicSetup(givenTopic(TOPIC_FQTN)); + + AafRole role = new AafRole(TOPIC_FQTN, "subscriber"); + DmaapPerm subPerm = new DmaapPerm(TOPIC_PERM, ":topic." + TOPIC_FQTN, "sub"); + DmaapPerm viewPerm = new DmaapPerm(TOPIC_PERM, ":topic." + TOPIC_FQTN, "view"); + aafService.shouldAddGrant(new DmaapGrant(subPerm, role.getFullyQualifiedRole())); + aafService.shouldAddGrant(new DmaapGrant(viewPerm, role.getFullyQualifiedRole())); + } + + @Test + public void shouldCreateOnlyPermissionsWhenCreateTopicRolesIsFalse() { + aafTopicSetupService = new AafTopicSetupService(aafService, dmaapService, false); + + aafTopicSetupService.aafTopicSetup(givenTopic(TOPIC_FQTN)); + + aafService.shouldAddPerm(new DmaapPerm(TOPIC_PERM, ":topic." + TOPIC_FQTN, "pub")); + aafService.shouldAddPerm(new DmaapPerm(TOPIC_PERM, ":topic." + TOPIC_FQTN, "sub")); + aafService.shouldAddPerm(new DmaapPerm(TOPIC_PERM, ":topic." + TOPIC_FQTN, "view")); + aafService.shouldHaveNoRolesAndGrants(); + } + + @Test + public void shouldCreateOnlyPermissionsWhenTopicFqtnDoesntStartWithNsRoot() { + + String topicFqtn = "sample_topic"; + aafTopicSetupService.aafTopicSetup(givenTopic(topicFqtn)); + + aafService.shouldAddPerm(new DmaapPerm(TOPIC_PERM, ":topic." + topicFqtn, "pub")); + aafService.shouldAddPerm(new DmaapPerm(TOPIC_PERM, ":topic." + topicFqtn, "sub")); + aafService.shouldAddPerm(new DmaapPerm(TOPIC_PERM, ":topic." + topicFqtn, "view")); + aafService.shouldHaveNoRolesAndGrants(); + } + + @Test + public void shouldHandleExceptionWhenTopicSnRootIsNotDefined() { + Dmaap dmaap = new Dmaap(); + dmaap.setTopicNsRoot(null); + given(dmaapService.getDmaap()).willReturn(dmaap); + + ApiError apiError = aafTopicSetupService.aafTopicSetup(givenTopic(TOPIC_FQTN)); + + assertErrorStatus(apiError, INTERNAL_SERVER_ERROR); + } + + @Test + public void shouldHandleExceptionWhenPermissionCreationWasFailed() { + aafService.givenAddPermStatus(NOT_FOUND); + + ApiError apiError = aafTopicSetupService.aafTopicSetup(givenTopic(TOPIC_FQTN)); + + assertErrorStatus(apiError, INTERNAL_SERVER_ERROR); + } + + @Test + public void shouldHandleExceptionWhenNamespaceCreationWasFailed() { + aafService.givenAddNamespaceStatus(NOT_FOUND); + + ApiError apiError = aafTopicSetupService.aafTopicSetup(givenTopic(TOPIC_FQTN)); + + assertErrorStatus(apiError, INTERNAL_SERVER_ERROR); + } + + @Test + public void shouldHandleExceptionWhenRoleCreationWasFailed() { + aafService.givenAddRoleStatus(NOT_FOUND); + + ApiError apiError = aafTopicSetupService.aafTopicSetup(givenTopic(TOPIC_FQTN)); + + assertErrorStatus(apiError, INTERNAL_SERVER_ERROR); + } + + @Test + public void shouldHandleExceptionWhenGrantPermToRoleWasFailed() { + aafService.givenAddGrantStatus(NOT_FOUND); + + ApiError apiError = aafTopicSetupService.aafTopicSetup(givenTopic(TOPIC_FQTN)); + + assertErrorStatus(apiError, NOT_FOUND); + } + + private Topic givenTopic(String topicFqtn) { + Topic topic = new Topic(); + topic.setFqtn(topicFqtn); + return topic; + } + + private void assertOkStatus(ApiError apiError) { + assertTrue(apiError.is2xx()); + assertEquals("OK", apiError.getMessage()); + } + + private void assertErrorStatus(ApiError apiError, int code) { + assertEquals(code, apiError.getCode()); + } + + private class AafServiceStub implements AafService { + + private AafNamespace namespace; + private List perms = newArrayList(); + private List roles = newArrayList(); + private List grants = newArrayList(); + private int addNamespaceStatus = CREATED; + private int addGrantStatus = CREATED; + private int addRoleStatus = CREATED; + private int addPermStatus = CREATED; + + @Override + public String getIdentity() { + return IDENTITY; + } + + @Override + public int addPerm(DmaapPerm perm) { + this.perms.add(perm); + return addPermStatus; + } + + @Override + public int addGrant(DmaapGrant grant) { + grants.add(grant); + return addGrantStatus; + } + + @Override + public int addUserRole(AafUserRole ur) { + throw new UnsupportedOperationException(); + } + + @Override + public int delGrant(DmaapGrant grant) { + throw new UnsupportedOperationException(); + } + + @Override + public int addRole(AafRole role) { + this.roles.add(role); + return addRoleStatus; + } + + @Override + public int addNamespace(AafNamespace namespace) { + this.namespace = namespace; + return addNamespaceStatus; + } + + void givenReturnCode(int status) { + this.addNamespaceStatus = status; + this.addGrantStatus = status; + this.addRoleStatus = status; + this.addPermStatus = status; + } + + void givenAddNamespaceStatus(int addNamespaceStatus) { + this.addNamespaceStatus = addNamespaceStatus; + } + + void givenAddGrantStatus(int addGrantStatus) { + this.addGrantStatus = addGrantStatus; + } + + void givenAddRoleStatus(int addRoleStatus) { + this.addRoleStatus = addRoleStatus; + } + + void givenAddPermStatus(int addPermStatus) { + this.addPermStatus = addPermStatus; + } + + void shouldAddPerm(DmaapPerm perm) { + assertTrue(perms.contains(perm)); + } + + void shouldAddNamespace(AafNamespace namespace) { + assertEquals(namespace, this.namespace); + } + + void shouldAddRole(AafRole role) { + assertTrue(roles.contains(role)); + } + + void shouldAddGrant(DmaapGrant grant) { + assertTrue(grants.contains(grant)); + } + + void shouldHaveNoRolesAndGrants() { + assertTrue(this.grants.isEmpty()); + assertTrue(this.roles.isEmpty()); + } + } +} \ No newline at end of file