*/
package org.opendaylight.bgpcep.pcep.topology.provider;
+import static com.google.common.base.Verify.verifyNotNull;
import static java.util.Objects.requireNonNull;
import com.google.common.util.concurrent.ListenableFuture;
import java.net.InetSocketAddress;
+import java.util.Collection;
+import java.util.Map;
import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
+import org.opendaylight.mdsal.binding.api.ClusteredDataTreeChangeListener;
import org.opendaylight.mdsal.binding.api.DataBroker;
+import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
+import org.opendaylight.mdsal.binding.api.DataTreeModification;
import org.opendaylight.mdsal.binding.api.ReadTransaction;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
import org.opendaylight.protocol.pcep.PCEPPeerProposal;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.pcep.sync.optimizations.rev200720.Tlvs3Builder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.pcep.sync.optimizations.rev200720.lsp.db.version.tlv.LspDbVersion;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.pcep.sync.optimizations.rev200720.speaker.entity.id.tlv.SpeakerEntityIdBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.config.rev200120.pcep.node.config.SessionConfig;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.ietf.stateful.rev200720.Tlvs1;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev181109.open.object.open.TlvsBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.pcep.config.rev181109.PcepNodeConfig;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.pcep.rev200120.Node1;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.pcep.rev200120.pcep.client.attributes.PathComputationClient;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.pcep.sync.optimizations.config.rev181109.PcepNodeSyncConfig;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeKey;
+import org.opendaylight.yangtools.concepts.AbstractRegistration;
+import org.opendaylight.yangtools.concepts.Registration;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-final class PCEPStatefulPeerProposal implements PCEPPeerProposal {
+final class PCEPStatefulPeerProposal extends AbstractRegistration implements PCEPPeerProposal {
+ private static final class SpeakerIdListener implements ClusteredDataTreeChangeListener<PcepNodeSyncConfig> {
+ final Map<NodeId, byte[]> map = new ConcurrentHashMap<>();
+ final Registration reg;
+
+ SpeakerIdListener(final DataBroker dataBroker, final InstanceIdentifier<Topology> topologyId) {
+ reg = dataBroker.registerDataTreeChangeListener(DataTreeIdentifier.create(
+ LogicalDatastoreType.CONFIGURATION, topologyId.child(Node.class).augmentation(PcepNodeConfig.class)
+ .child(SessionConfig.class).augmentation(PcepNodeSyncConfig.class)), this);
+ }
+
+ @Override
+ public void onDataTreeChanged(final Collection<DataTreeModification<PcepNodeSyncConfig>> changes) {
+ for (var change : changes) {
+ final var nodeId = verifyNotNull(change.getRootPath().getRootIdentifier().firstKeyOf(Node.class))
+ .getNodeId();
+ final var config = change.getRootNode().getDataAfter();
+ if (config != null) {
+ final var speakerEntityId = config.getSpeakerEntityIdValue();
+ if (speakerEntityId != null) {
+ map.put(nodeId, speakerEntityId);
+ continue;
+ }
+ }
+ map.remove(nodeId);
+ }
+ }
+ }
+
private static final Logger LOG = LoggerFactory.getLogger(PCEPStatefulPeerProposal.class);
private final InstanceIdentifier<Topology> topologyId;
+ private final SpeakerIdListener speakerIds;
private final DataBroker dataBroker;
- private volatile SpeakerIdMapping speakerIds;
-
- PCEPStatefulPeerProposal(final DataBroker dataBroker, final InstanceIdentifier<Topology> topologyId,
- final SpeakerIdMapping speakerIds) {
+ PCEPStatefulPeerProposal(final DataBroker dataBroker, final InstanceIdentifier<Topology> topologyId) {
this.dataBroker = requireNonNull(dataBroker);
this.topologyId = requireNonNull(topologyId);
- // FIXME: BGPCEP-983: we should acquire this through DTCL
- this.speakerIds = requireNonNull(speakerIds);
- }
-
- void setSpeakerIds(final SpeakerIdMapping speakerIds) {
- this.speakerIds = requireNonNull(speakerIds);
+ speakerIds = new SpeakerIdListener(dataBroker, topologyId);
}
@Override
return;
}
- // FIXME: BGPCEP-989: acquire this information via a DTCL and perform a simple lookup only
final var addr = address.getAddress();
+ final var nodeId = ServerSessionManager.createNodeId(addr);
+ // FIXME: BGPCEP-989: acquire this information via a DTCL and perform a simple lookup only
Optional<LspDbVersion> result = Optional.empty();
-
try (ReadTransaction rTx = dataBroker.newReadOnlyTransaction()) {
// FIXME: we should be listening for this configuration and keep a proper cache
final ListenableFuture<Optional<LspDbVersion>> future = rTx.read(LogicalDatastoreType.OPERATIONAL,
- topologyId.child(Node.class, new NodeKey(ServerSessionManager.createNodeId(addr)))
+ topologyId.child(Node.class, new NodeKey(nodeId))
.augmentation(Node1.class).child(PathComputationClient.class)
.augmentation(PathComputationClient1.class).child(LspDbVersion.class));
try {
result = future.get();
} catch (final InterruptedException | ExecutionException e) {
LOG.warn("Failed to read toplogy {}.", InstanceIdentifier.keyOf(topologyId), e);
-
}
}
- final var speakerId = speakerIds.speakerIdForAddress(addr);
+ final var speakerId = speakerIds.map.get(nodeId);
if (speakerId == null && !result.isPresent()) {
return;
}
}
openBuilder.addAugmentation(syncBuilder.build()).build();
}
+
+ @Override
+ protected void removeRegistration() {
+ speakerIds.reg.close();
+ }
}
import static java.util.Objects.requireNonNull;
-import com.google.common.collect.ImmutableMap;
import com.google.common.net.InetAddresses;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.PortNumber;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.pcep.config.rev181109.PcepNodeConfig;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.pcep.config.rev181109.PcepTopologyTypeConfig;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.pcep.sync.optimizations.config.rev181109.PcepNodeSyncConfig;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeKey;
import org.opendaylight.yangtools.concepts.Immutable;
final class PCEPTopologyConfiguration implements Immutable {
- private final @NonNull SpeakerIdMapping speakerIds;
private final @NonNull InetSocketAddress address;
private final @NonNull KeyMapping keys;
private final short rpcTimeout;
PCEPTopologyConfiguration(final @NonNull InetSocketAddress address, final short rpcTimeout,
- final @NonNull KeyMapping keys, final @NonNull SpeakerIdMapping speakerIds) {
+ final @NonNull KeyMapping keys) {
this.address = requireNonNull(address);
this.keys = requireNonNull(keys);
- this.speakerIds = requireNonNull(speakerIds);
this.rpcTimeout = rpcTimeout;
}
return new PCEPTopologyConfiguration(
getInetSocketAddress(sessionConfig.getListenAddress(), sessionConfig.getListenPort()),
- sessionConfig.getRpcTimeout(), constructKeys(topology.getNode()), contructSpeakersId(topology.getNode()));
+ sessionConfig.getRpcTimeout(), constructKeys(topology.getNode()));
}
short getRpcTimeout() {
return keys;
}
- @NonNull SpeakerIdMapping getSpeakerIds() {
- return speakerIds;
- }
-
private static @NonNull KeyMapping constructKeys(final @Nullable Map<NodeKey, Node> nodes) {
if (nodes == null) {
return KeyMapping.of();
return KeyMapping.of(passwords);
}
- private static @NonNull SpeakerIdMapping contructSpeakersId(final @Nullable Map<NodeKey, Node> nodes) {
- if (nodes == null) {
- return SpeakerIdMapping.of();
- }
-
- final var builder = ImmutableMap.<InetAddress, byte[]>builder();
- for (var node : nodes.values()) {
- if (node != null) {
- final var nodeConfig = node.augmentation(PcepNodeConfig.class);
- if (nodeConfig != null) {
- final var sessionConfig = nodeConfig.getSessionConfig();
- if (sessionConfig != null) {
- final var nodeSyncConfig = sessionConfig.augmentation(PcepNodeSyncConfig.class);
- if (nodeSyncConfig != null) {
- final var speakerEntityId = nodeSyncConfig.getSpeakerEntityIdValue();
- if (speakerEntityId != null) {
- builder.put(nodeAddress(node), speakerEntityId);
- }
- }
- }
- }
- }
- }
-
- return SpeakerIdMapping.copyOf(builder.build());
- }
-
private static InetAddress nodeAddress(final Node node) {
return InetAddresses.forString(node.getNodeId().getValue());
}
.filter(nodeId -> !Arrays.equals(currentKeys.get(nodeId), newKeys.get(nodeId)))
.collect(Collectors.toUnmodifiableList());
- proposal.setSpeakerIds(newConfiguration.getSpeakerIds());
manager.setRpcTimeout(newConfiguration.getRpcTimeout());
if (!outdatedNodes.isEmpty()) {
LOG.info("Topology Provider {} updating {} TCP-MD5 keys", topologyId(), outdatedNodes.size());
return;
}
- proposal = new PCEPStatefulPeerProposal(dependencies.getDataBroker(), instanceIdentifier,
- currentConfig.getSpeakerIds());
+ proposal = new PCEPStatefulPeerProposal(dependencies.getDataBroker(), instanceIdentifier);
LOG.info("PCEP Topology Provider {} starting server channel", topologyId());
final var channelFuture = dependencies.getPCEPDispatcher().createServer(
@Holding("this")
private void disableManager(final SettableFuture<Empty> future) {
+ proposal.close();
proposal = null;
final var managerStop = manager.stop();
manager = null;
+++ /dev/null
-/*
- * Copyright (c) 2017 AT&T Intellectual Property. 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.bgpcep.pcep.topology.provider;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Maps;
-import java.net.InetAddress;
-import java.util.Map;
-import org.eclipse.jdt.annotation.NonNull;
-import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.yangtools.concepts.Immutable;
-
-final class SpeakerIdMapping implements Immutable {
- private static final @NonNull SpeakerIdMapping EMPTY = new SpeakerIdMapping(ImmutableMap.of());
-
- private final ImmutableMap<InetAddress, byte[]> map;
-
- private SpeakerIdMapping(final Map<InetAddress, byte[]> map) {
- this.map = ImmutableMap.copyOf(map);
- }
-
- static @NonNull SpeakerIdMapping of() {
- return EMPTY;
- }
-
- static @NonNull SpeakerIdMapping copyOf(final Map<InetAddress, byte[]> map) {
- return map.isEmpty() ? of()
- // Defensive: disconnect byte[]s from caller
- : new SpeakerIdMapping(Maps.transformValues(map, byte[]::clone));
- }
-
- byte @Nullable [] speakerIdForAddress(final InetAddress address) {
- final byte[] found = map.get(address);
- // Defensive: do not leak byte[]
- return found == null ? null : found.clone();
- }
-}
\ No newline at end of file
*/
package org.opendaylight.bgpcep.pcep.topology.provider;
-import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
import com.google.common.util.concurrent.FluentFuture;
import java.net.InetSocketAddress;
-import java.util.Map;
+import java.util.List;
import java.util.Optional;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
import org.opendaylight.mdsal.binding.api.DataBroker;
+import org.opendaylight.mdsal.binding.api.DataObjectModification;
+import org.opendaylight.mdsal.binding.api.DataTreeChangeListener;
+import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
+import org.opendaylight.mdsal.binding.api.DataTreeModification;
import org.opendaylight.mdsal.binding.api.ReadTransaction;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.pcep.sync.optimizations.rev200720.Stateful1Builder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.pcep.sync.optimizations.rev200720.Tlvs3;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.pcep.sync.optimizations.rev200720.lsp.db.version.tlv.LspDbVersion;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.pcep.sync.optimizations.rev200720.lsp.db.version.tlv.LspDbVersionBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.pcep.sync.optimizations.rev200720.speaker.entity.id.tlv.SpeakerEntityIdBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.ietf.stateful.rev200720.Tlvs1Builder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.ietf.stateful.rev200720.stateful.capability.tlv.StatefulBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev181109.open.object.open.TlvsBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.topology.pcep.sync.optimizations.config.rev181109.PcepNodeSyncConfigBuilder;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeKey;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.opendaylight.yangtools.yang.common.Uint64;
+@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class PCEPStatefulPeerProposalTest {
private static final InstanceIdentifier<Topology> TOPOLOGY_IID = InstanceIdentifier.create(NetworkTopology.class)
.child(Topology.class, new TopologyKey(new TopologyId("topology")));
@Mock
private DataBroker dataBroker;
@Mock
+ private ListenerRegistration<?> listenerReg;
+ @Mock
private FluentFuture<Optional<LspDbVersion>> listenableFutureMock;
@Mock
private ReadTransaction rt;
+
+ private ArgumentCaptor<DataTreeChangeListener<?>> captor;
private TlvsBuilder tlvsBuilder;
- @SuppressWarnings("unchecked")
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
tlvsBuilder = new TlvsBuilder().addAugmentation(new Tlvs1Builder()
.setStateful(new StatefulBuilder().addAugmentation(new Stateful1Builder().build()).build())
.build());
+ captor = ArgumentCaptor.forClass(DataTreeChangeListener.class);
+ doReturn(listenerReg).when(dataBroker).registerDataTreeChangeListener(any(), captor.capture());
+ doNothing().when(listenerReg).close();
doReturn(rt).when(dataBroker).newReadOnlyTransaction();
doNothing().when(rt).close();
- doReturn(listenableFutureMock).when(rt)
- .read(any(LogicalDatastoreType.class), any(InstanceIdentifier.class));
- doReturn(true).when(listenableFutureMock).isDone();
- doAnswer(invocation -> {
- final Runnable runnable = (Runnable) invocation.getArguments()[0];
- runnable.run();
- return null;
- }).when(listenableFutureMock).addListener(any(Runnable.class), any(Executor.class));
+ doReturn(listenableFutureMock).when(rt).read(any(), any());
}
@Test
- public void testSetPeerProposalSuccess() throws InterruptedException, ExecutionException {
+ public void testSetPeerProposalSuccess() throws Exception {
doReturn(Optional.of(LSP_DB_VERSION)).when(listenableFutureMock).get();
- final PCEPStatefulPeerProposal peerProposal = new PCEPStatefulPeerProposal(dataBroker, TOPOLOGY_IID,
- SpeakerIdMapping.of());
- peerProposal.setPeerSpecificProposal(ADDRESS, tlvsBuilder);
+ updateBuilder();
assertEquals(LSP_DB_VERSION, tlvsBuilder.augmentation(Tlvs3.class).getLspDbVersion());
}
@Test
- public void testSetPeerProposalWithEntityIdSuccess() throws InterruptedException, ExecutionException {
+ public void testSetPeerProposalWithEntityIdSuccess() throws Exception {
doReturn(Optional.of(LSP_DB_VERSION)).when(listenableFutureMock).get();
- final PCEPStatefulPeerProposal peerProposal = new PCEPStatefulPeerProposal(dataBroker, TOPOLOGY_IID,
- SpeakerIdMapping.copyOf(Map.of(ADDRESS.getAddress(), SPEAKER_ID)));
- peerProposal.setPeerSpecificProposal(ADDRESS, tlvsBuilder);
+
+ updateBuilder(() -> {
+ final var modification = mock(DataTreeModification.class);
+ doReturn(DataTreeIdentifier.create(LogicalDatastoreType.CONFIGURATION,
+ // not entirely accurate, but works well enough
+ TOPOLOGY_IID.child(Node.class, new NodeKey(ServerSessionManager.createNodeId(ADDRESS.getAddress())))))
+ .when(modification).getRootPath();
+
+ final var modificationRoot = mock(DataObjectModification.class);
+ doReturn(new PcepNodeSyncConfigBuilder().setSpeakerEntityIdValue(SPEAKER_ID).build()).when(modificationRoot)
+ .getDataAfter();
+ doReturn(modificationRoot).when(modification).getRootNode();
+
+ captor.getValue().onDataTreeChanged(List.of(modification));
+ });
final Tlvs3 aug = tlvsBuilder.augmentation(Tlvs3.class);
+ assertNotNull(aug);
assertEquals(LSP_DB_VERSION, aug.getLspDbVersion());
- assertArrayEquals(SPEAKER_ID, aug.getSpeakerEntityId().getSpeakerEntityIdValue());
+ assertEquals(new SpeakerEntityIdBuilder().setSpeakerEntityIdValue(SPEAKER_ID).build(),
+ aug.getSpeakerEntityId());
}
@Test
- public void testSetPeerProposalAbsent() throws InterruptedException, ExecutionException {
+ public void testSetPeerProposalAbsent() throws Exception {
doReturn(Optional.empty()).when(listenableFutureMock).get();
- final PCEPStatefulPeerProposal peerProposal = new PCEPStatefulPeerProposal(dataBroker, TOPOLOGY_IID,
- SpeakerIdMapping.of());
- peerProposal.setPeerSpecificProposal(ADDRESS, tlvsBuilder);
+ updateBuilder();
assertNull(tlvsBuilder.augmentation(Tlvs3.class));
}
@Test
- public void testSetPeerProposalFailure() throws InterruptedException, ExecutionException {
+ public void testSetPeerProposalFailure() throws Exception {
doThrow(new InterruptedException()).when(listenableFutureMock).get();
- final PCEPStatefulPeerProposal peerProposal = new PCEPStatefulPeerProposal(dataBroker, TOPOLOGY_IID,
- SpeakerIdMapping.of());
- peerProposal.setPeerSpecificProposal(ADDRESS, tlvsBuilder);
+ updateBuilder();
assertNull(tlvsBuilder.augmentation(Tlvs3.class));
}
+
+ private void updateBuilder() {
+ updateBuilder(null);
+ }
+
+ private void updateBuilder(final Runnable customizer) {
+ try (var proposal = new PCEPStatefulPeerProposal(dataBroker, TOPOLOGY_IID)) {
+ if (customizer != null) {
+ customizer.run();
+ }
+ proposal.setPeerSpecificProposal(ADDRESS, tlvsBuilder);
+ }
+ }
}