From c2b28f6d55559e66b74922cd6d28d8ca85a01f0e Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Fri, 20 Jan 2017 23:57:50 +0100 Subject: [PATCH] BUG-7611: Add a simple DOMEntityOwnershipService This adds a very simplistic DOMEntityOwnershipService, which assumes it is the only thing in town. Local node always owns the entities registered. Listeners are dispatched synchronously as registrations occur. Change-Id: Ia0a4dbcc159d80b4efd2463722867eaf6a791780 Signed-off-by: Robert Varga --- common/artifacts/pom.xml | 75 ++++--- entityownership/mdsal-eos-dom-simple/pom.xml | 111 ++++++++++ .../SimpleDOMEntityOwnershipService.java | 203 ++++++++++++++++++ .../SimpleDOMEntityOwnershipServiceTest.java | 127 +++++++++++ entityownership/pom.xml | 1 + 5 files changed, 482 insertions(+), 35 deletions(-) create mode 100644 entityownership/mdsal-eos-dom-simple/pom.xml create mode 100644 entityownership/mdsal-eos-dom-simple/src/main/java/org/opendaylight/mdsal/eos/dom/simple/SimpleDOMEntityOwnershipService.java create mode 100644 entityownership/mdsal-eos-dom-simple/src/test/java/org/opendaylight/mdsal/eos/dom/simple/SimpleDOMEntityOwnershipServiceTest.java diff --git a/common/artifacts/pom.xml b/common/artifacts/pom.xml index f0bb4689f5..0c6c4fd561 100644 --- a/common/artifacts/pom.xml +++ b/common/artifacts/pom.xml @@ -26,41 +26,41 @@ - - org.opendaylight.mdsal - features-mdsal - features - ${project.version} - xml - - - - - org.opendaylight.mdsal - mdsal-common-api - ${project.version} - - - - org.opendaylight.mdsal - mdsal-dom-api - ${project.version} - - - org.opendaylight.mdsal - mdsal-dom-spi - ${project.version} - - - org.opendaylight.mdsal - mdsal-dom-broker - ${project.version} - - - org.opendaylight.mdsal - mdsal-dom-inmemory-datastore - ${project.version} - + + org.opendaylight.mdsal + features-mdsal + features + ${project.version} + xml + + + + + org.opendaylight.mdsal + mdsal-common-api + ${project.version} + + + + org.opendaylight.mdsal + mdsal-dom-api + ${project.version} + + + org.opendaylight.mdsal + mdsal-dom-spi + ${project.version} + + + org.opendaylight.mdsal + mdsal-dom-broker + ${project.version} + + + org.opendaylight.mdsal + mdsal-dom-inmemory-datastore + ${project.version} + @@ -190,6 +190,11 @@ mdsal-eos-dom-api ${project.version} + + org.opendaylight.mdsal + mdsal-eos-dom-simple + ${project.version} + org.opendaylight.mdsal mdsal-eos-binding-api diff --git a/entityownership/mdsal-eos-dom-simple/pom.xml b/entityownership/mdsal-eos-dom-simple/pom.xml new file mode 100644 index 0000000000..2ccb436012 --- /dev/null +++ b/entityownership/mdsal-eos-dom-simple/pom.xml @@ -0,0 +1,111 @@ + + + + 4.0.0 + + org.opendaylight.odlparent + bundle-parent + 1.8.0-SNAPSHOT + + + + org.opendaylight.mdsal + mdsal-eos-dom-simple + 2.2.0-SNAPSHOT + bundle + + + + + org.opendaylight.mdsal + mdsal-artifacts + 2.2.0-SNAPSHOT + pom + import + + + org.opendaylight.yangtools + yangtools-artifacts + 1.1.0-SNAPSHOT + pom + import + + + + + + + com.google.guava + guava + + + org.opendaylight.mdsal + mdsal-eos-common-api + + + org.opendaylight.mdsal + mdsal-eos-common-spi + + + org.opendaylight.mdsal + mdsal-eos-dom-api + + + org.opendaylight.yangtools + yang-common + + + + org.kohsuke.metainf-services + metainf-services + true + + + + org.mockito + mockito-core + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + checkstyle.violationSeverity=error + + + + + + + scm:git:http://git.opendaylight.org/gerrit/controller.git + scm:git:ssh://git.opendaylight.org:29418/controller.git + HEAD + https://wiki.opendaylight.org/view/OpenDaylight_Controller:MD-SAL + + + + ${odl.site.url}/${project.groupId}/${stream}/${project.artifactId}/ + + + + opendaylight-site + ${nexus.site.url}/${project.artifactId}/ + + + + diff --git a/entityownership/mdsal-eos-dom-simple/src/main/java/org/opendaylight/mdsal/eos/dom/simple/SimpleDOMEntityOwnershipService.java b/entityownership/mdsal-eos-dom-simple/src/main/java/org/opendaylight/mdsal/eos/dom/simple/SimpleDOMEntityOwnershipService.java new file mode 100644 index 0000000000..f88d150832 --- /dev/null +++ b/entityownership/mdsal-eos-dom-simple/src/main/java/org/opendaylight/mdsal/eos/dom/simple/SimpleDOMEntityOwnershipService.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2017 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.eos.dom.simple; + +import static org.opendaylight.mdsal.eos.common.api.EntityOwnershipChangeState.LOCAL_OWNERSHIP_GRANTED; +import static org.opendaylight.mdsal.eos.common.api.EntityOwnershipChangeState.LOCAL_OWNERSHIP_LOST_NO_OWNER; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.MoreObjects; +import com.google.common.base.MoreObjects.ToStringHelper; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Multimap; +import com.google.common.collect.Table; +import java.util.Collection; +import java.util.UUID; +import javax.annotation.concurrent.GuardedBy; +import org.kohsuke.MetaInfServices; +import org.opendaylight.mdsal.eos.common.api.CandidateAlreadyRegisteredException; +import org.opendaylight.mdsal.eos.common.api.EntityOwnershipChangeState; +import org.opendaylight.mdsal.eos.common.api.EntityOwnershipState; +import org.opendaylight.mdsal.eos.dom.api.DOMEntity; +import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipCandidateRegistration; +import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipChange; +import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListener; +import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListenerRegistration; +import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipService; +import org.opendaylight.yangtools.concepts.AbstractObjectRegistration; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Simple {@link DOMEntityOwnershipService} operating as an isolated island. It has no awareness of the world outside + * of itself. + * + * @author Robert Varga + */ +@MetaInfServices +public final class SimpleDOMEntityOwnershipService implements DOMEntityOwnershipService { + private static final Logger LOG = LoggerFactory.getLogger(SimpleDOMEntityOwnershipService.class); + + @GuardedBy("entities") + private final Table entities = HashBasedTable.create(); + + @GuardedBy("listeners") + private final Multimap listeners = ArrayListMultimap.create(0, 1); + + private final UUID uuid; + + @VisibleForTesting + SimpleDOMEntityOwnershipService(final UUID uuid) { + this.uuid = Preconditions.checkNotNull(uuid); + } + + public SimpleDOMEntityOwnershipService() { + this(UUID.randomUUID()); + } + + @Override + public DOMEntityOwnershipCandidateRegistration registerCandidate(final DOMEntity entity) + throws CandidateAlreadyRegisteredException { + synchronized (entities) { + final DOMEntity prev = entities.get(entity.getType(), entity.getIdentifier()); + if (prev != null) { + throw new CandidateAlreadyRegisteredException(prev); + } + + entities.put(entity.getType(), entity.getIdentifier(), entity); + LOG.debug("{}: registered candidate {}", uuid, entity); + } + + notifyListeners(entity, LOCAL_OWNERSHIP_GRANTED); + return new EntityRegistration(entity); + } + + @Override + public DOMEntityOwnershipListenerRegistration registerListener(final String entityType, + final DOMEntityOwnershipListener listener) { + + final Collection owned; + synchronized (entities) { + owned = ImmutableList.copyOf(entities.row(entityType).values()); + LOG.trace("{}: acquired candidates {} for new listener {}", uuid, owned, listener); + } + + synchronized (listeners) { + listeners.put(entityType, listener); + } + + for (DOMEntity entity : owned) { + notifyListener(listener, new DOMEntityOwnershipChange(entity, LOCAL_OWNERSHIP_GRANTED)); + } + LOG.debug("{}: registered listener {}", uuid, listener); + return new ListenerRegistration(entityType, listener); + } + + @Override + public Optional getOwnershipState(final DOMEntity forEntity) { + return isCandidateRegistered(forEntity) ? Optional.of(EntityOwnershipState.IS_OWNER) : Optional.absent(); + } + + @Override + public boolean isCandidateRegistered(final DOMEntity forEntity) { + synchronized (entities) { + return entities.contains(forEntity.getType(), forEntity.getIdentifier()); + } + } + + private void removeEntity(final DOMEntity entity) { + synchronized (entities) { + entities.remove(entity.getType(), entity.getIdentifier()); + LOG.debug("{}: unregistered candidate {}", uuid, entity); + } + + notifyListeners(entity, LOCAL_OWNERSHIP_LOST_NO_OWNER); + } + + @SuppressWarnings("checkstyle:illegalCatch") + private void notifyListener(final DOMEntityOwnershipListener listener, final DOMEntityOwnershipChange change) { + try { + LOG.trace("{} notifying listener {} change {}", uuid, listener, change); + listener.ownershipChanged(change); + } catch (RuntimeException e) { + LOG.warn("{}: Listener {} change {} failed", uuid, listener, change, e); + } + } + + private void notifyListeners(final DOMEntity entity, final EntityOwnershipChangeState state) { + final DOMEntityOwnershipChange change = new DOMEntityOwnershipChange(entity, state); + + final Collection snap; + + synchronized (listeners) { + snap = ImmutableList.copyOf(listeners.get(entity.getType())); + } + + for (DOMEntityOwnershipListener listener : snap) { + notifyListener(listener, change); + } + } + + void unregisterListener(final ListenerRegistration reg) { + synchronized (listeners) { + listeners.remove(reg.getEntityType(), reg.getInstance()); + LOG.debug("{}: unregistered listener {}", uuid, reg.getInstance()); + } + } + + @Override + public String toString() { + final ToStringHelper h = MoreObjects.toStringHelper(SimpleDOMEntityOwnershipService.class).add("uuid", uuid); + + synchronized (entities) { + h.add("entities", entities); + } + synchronized (listeners) { + h.add("listeners", listeners); + } + + return h.toString(); + } + + private final class EntityRegistration extends AbstractObjectRegistration implements + DOMEntityOwnershipCandidateRegistration { + EntityRegistration(final DOMEntity entity) { + super(entity); + } + + @Override + protected void removeRegistration() { + removeEntity(getInstance()); + } + } + + private final class ListenerRegistration extends AbstractObjectRegistration + implements DOMEntityOwnershipListenerRegistration { + private final String entityType; + + ListenerRegistration(final String entityType, final DOMEntityOwnershipListener listener) { + super(listener); + this.entityType = Preconditions.checkNotNull(entityType); + } + + @Override + public String getEntityType() { + return entityType; + } + + @Override + protected void removeRegistration() { + unregisterListener(this); + } + } +} diff --git a/entityownership/mdsal-eos-dom-simple/src/test/java/org/opendaylight/mdsal/eos/dom/simple/SimpleDOMEntityOwnershipServiceTest.java b/entityownership/mdsal-eos-dom-simple/src/test/java/org/opendaylight/mdsal/eos/dom/simple/SimpleDOMEntityOwnershipServiceTest.java new file mode 100644 index 0000000000..63e0f01243 --- /dev/null +++ b/entityownership/mdsal-eos-dom-simple/src/test/java/org/opendaylight/mdsal/eos/dom/simple/SimpleDOMEntityOwnershipServiceTest.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2017 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.eos.dom.simple; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +import com.google.common.base.Optional; +import java.util.UUID; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.opendaylight.mdsal.eos.common.api.CandidateAlreadyRegisteredException; +import org.opendaylight.mdsal.eos.common.api.EntityOwnershipChangeState; +import org.opendaylight.mdsal.eos.common.api.EntityOwnershipState; +import org.opendaylight.mdsal.eos.dom.api.DOMEntity; +import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipCandidateRegistration; +import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipChange; +import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListener; +import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListenerRegistration; +import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipService; + +public class SimpleDOMEntityOwnershipServiceTest { + private static final String FOO_TYPE = "foo"; + private static final String BAR_TYPE = "bar"; + + private static final DOMEntity FOO_FOO_ENTITY = new DOMEntity(FOO_TYPE, "foo"); + private static final DOMEntity FOO_BAR_ENTITY = new DOMEntity(FOO_TYPE, "bar"); + + private DOMEntityOwnershipService service; + + @Before + public void setUp() { + service = new SimpleDOMEntityOwnershipService(); + } + + @Test + public void testNonExistingEntity() { + assertFalse(service.isCandidateRegistered(FOO_FOO_ENTITY)); + final Optional state = service.getOwnershipState(FOO_FOO_ENTITY); + assertNotNull(state); + assertFalse(state.isPresent()); + } + + @Test + public void testExistingEntity() throws CandidateAlreadyRegisteredException { + final DOMEntityOwnershipCandidateRegistration reg = service.registerCandidate(FOO_FOO_ENTITY); + assertNotNull(reg); + + assertTrue(service.isCandidateRegistered(FOO_FOO_ENTITY)); + assertFalse(service.isCandidateRegistered(FOO_BAR_ENTITY)); + + final Optional state = service.getOwnershipState(FOO_FOO_ENTITY); + assertNotNull(state); + assertTrue(state.isPresent()); + assertEquals(EntityOwnershipState.IS_OWNER, state.get()); + + reg.close(); + assertFalse(service.isCandidateRegistered(FOO_FOO_ENTITY)); + } + + @Test(expected = CandidateAlreadyRegisteredException.class) + public void testDuplicateRegistration() throws CandidateAlreadyRegisteredException { + final DOMEntityOwnershipCandidateRegistration reg = service.registerCandidate(FOO_FOO_ENTITY); + assertNotNull(reg); + + // Should throw + service.registerCandidate(FOO_FOO_ENTITY); + } + + @Test + public void testListener() throws CandidateAlreadyRegisteredException { + final DOMEntityOwnershipCandidateRegistration entityReg = service.registerCandidate(FOO_FOO_ENTITY); + assertNotNull(entityReg); + + // Mismatched type, not triggered + final DOMEntityOwnershipListener barListener = mock(DOMEntityOwnershipListener.class); + doNothing().when(barListener).ownershipChanged(any(DOMEntityOwnershipChange.class)); + final DOMEntityOwnershipListenerRegistration barReg = service.registerListener(BAR_TYPE, barListener); + verifyZeroInteractions(barListener); + + // Matching type should be triggered + final DOMEntityOwnershipListener fooListener = mock(DOMEntityOwnershipListener.class); + doNothing().when(fooListener).ownershipChanged(any(DOMEntityOwnershipChange.class)); + final DOMEntityOwnershipListenerRegistration fooReg = service.registerListener(FOO_TYPE, fooListener); + final ArgumentCaptor fooCaptor = ArgumentCaptor.forClass( + DOMEntityOwnershipChange.class); + verify(fooListener).ownershipChanged(fooCaptor.capture()); + + DOMEntityOwnershipChange fooChange = fooCaptor.getValue(); + assertEquals(FOO_FOO_ENTITY, fooChange.getEntity()); + assertEquals(EntityOwnershipChangeState.LOCAL_OWNERSHIP_GRANTED, fooChange.getState()); + + reset(fooListener); + doNothing().when(fooListener).ownershipChanged(any(DOMEntityOwnershipChange.class)); + entityReg.close(); + verifyZeroInteractions(barListener); + verify(fooListener).ownershipChanged(fooCaptor.capture()); + fooChange = fooCaptor.getValue(); + assertEquals(FOO_FOO_ENTITY, fooChange.getEntity()); + assertEquals(EntityOwnershipChangeState.LOCAL_OWNERSHIP_LOST_NO_OWNER, fooChange.getState()); + + fooReg.close(); + barReg.close(); + } + + @Test + public void testToString() throws CandidateAlreadyRegisteredException { + final UUID uuid = UUID.randomUUID(); + final String expected = String.format("SimpleDOMEntityOwnershipService{uuid=%s, entities={}, listeners={}}", + uuid); + assertEquals(expected, new SimpleDOMEntityOwnershipService(uuid).toString()); + } +} diff --git a/entityownership/pom.xml b/entityownership/pom.xml index df0830f5ce..341ec9d0f1 100644 --- a/entityownership/pom.xml +++ b/entityownership/pom.xml @@ -28,6 +28,7 @@ mdsal-eos-common-api mdsal-eos-common-spi mdsal-eos-dom-api + mdsal-eos-dom-simple mdsal-eos-binding-api mdsal-eos-binding-adapter -- 2.36.6