Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b697e64
version changes
abhijith-css Jul 9, 2025
dc64a53
Merge pull request #1 from Controller-Project/origin/dev_v1
abhijith-css Jul 9, 2025
51a495e
version changes
abhijith-css Jul 9, 2025
938ab7c
packet handler issue fix
abhijith-css Jul 9, 2025
bb56ec4
Merge pull request #2 from Controller-Project/dev_packet_handler
abhijith-css Jul 9, 2025
ced9ffb
loopremover fixed
i-am-arathypm Jul 9, 2025
d58d590
Merge pull request #3 from Controller-Project/loopremover
i-am-arathypm Jul 9, 2025
959ff39
Arp changes
abhijith-css Jul 9, 2025
5064930
Merge branch 'dev' of git@github.com:Controller-Project/l2switch_mast…
abhijith-css Jul 9, 2025
8d2191c
Loopremover fix
i-am-arathypm Jul 9, 2025
0ea7c7f
Merge pull request #6 from Controller-Project/T1
i-am-arathypm Jul 9, 2025
fa4a7a1
Merge branch 'dev' of git@github.com:Controller-Project/l2switch_mast…
abhijith-css Jul 9, 2025
75775c6
addresstracker fix
i-am-arathypm Jul 10, 2025
d4f9af3
Merge pull request #7 from Controller-Project/addrTrack
i-am-arathypm Jul 10, 2025
e2510fe
test error fix
abhijith-css Jul 10, 2025
0e674ff
Merge pull request #8 from Controller-Project/dev_arp_issue-fix
abhijith-css Jul 10, 2025
3785ab9
Merge remote-tracking branch 'origin/dev' into dev_arp_issue-fix
abhijith-css Jul 10, 2025
306ad71
addresstracker test fix
i-am-arathypm Jul 10, 2025
8cd0d6f
Merge pull request #9 from Controller-Project/addrTrack_2
i-am-arathypm Jul 10, 2025
c0d61be
version changes
abhijith-css Jul 10, 2025
d39a23f
l2switch-main fixed
i-am-arathypm Jul 10, 2025
62b690b
Merge pull request #10 from Controller-Project/l2_main
i-am-arathypm Jul 10, 2025
276222d
host tracker
abhijith-css Jul 10, 2025
a655fee
Merge branch 'dev' of github.com:Controller-Project/l2switch_master i…
abhijith-css Jul 11, 2025
79a0723
Merge pull request #11 from Controller-Project/dev_arp_issue-fix
abhijith-css Jul 11, 2025
c3fb110
l2switch main fixed
i-am-arathypm Jul 11, 2025
e5a8d9a
package changed according to the new mdsal
i-am-arathypm Jul 11, 2025
4806e67
Merge pull request #12 from Controller-Project/dev
i-am-arathypm Jul 11, 2025
c60e1db
package change
abhijith-css Aug 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,14 @@
*/
package org.opendaylight.l2switch.addresstracker.addressobserver;

import com.google.common.util.concurrent.FluentFuture;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicLong;

import org.opendaylight.mdsal.binding.api.DataBroker;
import org.opendaylight.mdsal.binding.api.ReadTransaction;
import org.opendaylight.mdsal.binding.api.WriteTransaction;
Expand All @@ -32,153 +29,159 @@
import org.opendaylight.yang.gen.v1.urn.opendaylight.address.tracker.rev140617.address.node.connector.AddressesKey;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorRef;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector;
import org.opendaylight.yangtools.binding.DataObjectIdentifier;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.opendaylight.yangtools.yang.common.Uint64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.util.concurrent.FluentFuture;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.MoreExecutors;

/**
* AddressObservationWriter manages the MD-SAL data tree for address
* observations (mac, ip) on each node-connector.
*/
public class AddressObservationWriter {
private static final Logger LOG = LoggerFactory.getLogger(AddressObservationWriter.class);

private static final class NodeConnectorLock {
}

private final AtomicLong addressKey = new AtomicLong(0);
private long timestampUpdateInterval;
private final DataBroker dataService;
private final Map<NodeConnectorRef, NodeConnectorLock> lockMap = new ConcurrentHashMap<>();
private final Map<NodeConnectorLock, FluentFuture<CommitInfo>> futureMap = new ConcurrentHashMap<>();

/**
* Construct an AddressTracker with the specified inputs.
*
* @param dataService
* The DataBrokerService for the AddressTracker
*/
public AddressObservationWriter(DataBroker dataService) {
this.dataService = dataService;
}

public void setTimestampUpdateInterval(long timestampUpdateInterval) {
this.timestampUpdateInterval = timestampUpdateInterval;
}

/**
* Add addresses into the MD-SAL data tree.
*
* @param macAddress
* The MacAddress of the new L2Address object
* @param ipAddress
* The Source Protocol Address
* @param nodeConnectorRef
* The NodeConnectorRef of the new L2Address object
*/
public void addAddress(MacAddress macAddress, IpAddress ipAddress, NodeConnectorRef nodeConnectorRef) {
if (macAddress == null || ipAddress == null || nodeConnectorRef == null) {
return;
}

// get the lock for given node connector so at a time only one
// observation can be made on a node connector
NodeConnectorLock nodeConnectorLock = lockMap.computeIfAbsent(nodeConnectorRef, key -> new NodeConnectorLock());

synchronized (nodeConnectorLock) {
// Ensure previous transaction finished writing to the db
FluentFuture<CommitInfo> future = futureMap.get(nodeConnectorLock);
if (future != null) {
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
LOG.error("Exception while waiting for previous transaction to finish", e);
}
}

// Initialize builders
long now = new Date().getTime();
final AddressCapableNodeConnectorBuilder acncBuilder = new AddressCapableNodeConnectorBuilder();
final AddressesBuilder addressBuilder = new AddressesBuilder().setIp(ipAddress).setMac(macAddress)
.setFirstSeen(now).setLastSeen(now);

// Read existing address observations from data tree
final FluentFuture<Optional<NodeConnector>> readFuture;
try (ReadTransaction readTransaction = dataService.newReadOnlyTransaction()) {
readFuture = readTransaction.read(LogicalDatastoreType.OPERATIONAL,
(InstanceIdentifier<NodeConnector>) nodeConnectorRef.getValue());
}

final NodeConnector nc;
try {
final Optional<NodeConnector> dataObjectOptional = readFuture.get();
if (dataObjectOptional.isEmpty()) {
return;
}
nc = dataObjectOptional.orElseThrow();
} catch (InterruptedException | ExecutionException e) {
LOG.error("Error reading node connector {}", nodeConnectorRef.getValue());
throw new RuntimeException("Error reading from operational store, node connector : " + nodeConnectorRef,
e);
}
AddressCapableNodeConnector acnc = nc.augmentation(AddressCapableNodeConnector.class);

// FIXME: This block is quite inefficient: we have a unique key and yet we end up re-merging the entire
// 'addresses' list over and over again -- which is costly due to the HashMap copy below as well as
// the resulting merge() operation. After issuing the read, we should determine the key of the
// matching entry (or allocate a new key) and issue a simple put() on only the key.
final Map<AddressesKey, Addresses> addresses;
if (acnc != null) {
// Address observations exist
addresses = new HashMap<>(acnc.nonnullAddresses());
// Search for this mac-ip pair in the existing address observations & update last-seen timestamp
for (Addresses existing : addresses.values()) {
if (ipAddress.equals(existing.getIp()) && macAddress.equals(existing.getMac())) {
if ((now - existing.getLastSeen() <= timestampUpdateInterval)) {
// Update interval has not elapsed, do not run update
return;
}
addressBuilder.setFirstSeen(existing.getFirstSeen()).withKey(existing.key());
break;
}
}
} else {
// Address observations don't exist, so create the list
addresses = new HashMap<>();
}

if (addressBuilder.key() == null) {
addressBuilder.withKey(new AddressesKey(Uint64.fromLongBits(addressKey.getAndIncrement())));
}

// Add as an augmentation
final Addresses address = addressBuilder.build();
addresses.put(address.key(), address);
acncBuilder.setAddresses(addresses);

// build Instance Id for AddressCapableNodeConnector
InstanceIdentifier<AddressCapableNodeConnector> addressCapableNcInstanceId =
((InstanceIdentifier<NodeConnector>) nodeConnectorRef
.getValue()).augmentation(AddressCapableNodeConnector.class);
final WriteTransaction writeTransaction = dataService.newWriteOnlyTransaction();
// Update this AddressCapableNodeConnector in the MD-SAL data tree
writeTransaction.merge(LogicalDatastoreType.OPERATIONAL, addressCapableNcInstanceId, acncBuilder.build());

final FluentFuture writeTxResultFuture = writeTransaction.commit();
Futures.addCallback(writeTxResultFuture, new FutureCallback<CommitInfo>() {
@Override
public void onSuccess(CommitInfo notUsed) {
LOG.debug("AddressObservationWriter write successful for tx :{}", writeTransaction.getIdentifier());
}

@Override
public void onFailure(Throwable throwable) {
LOG.error("AddressObservationWriter write transaction {} failed", writeTransaction.getIdentifier(),
throwable.getCause());
}
}, MoreExecutors.directExecutor());
futureMap.put(nodeConnectorLock, writeTxResultFuture);
}
}
private static final Logger LOG = LoggerFactory.getLogger(AddressObservationWriter.class);

private static final class NodeConnectorLock {
}

private final AtomicLong addressKey = new AtomicLong(0);
private long timestampUpdateInterval;
private final DataBroker dataService;
private final Map<NodeConnectorRef, NodeConnectorLock> lockMap = new ConcurrentHashMap<>();
private final Map<NodeConnectorLock, FluentFuture<CommitInfo>> futureMap = new ConcurrentHashMap<>();

/**
* Construct an AddressTracker with the specified inputs.
*
* @param dataService The DataBrokerService for the AddressTracker
*/
public AddressObservationWriter(DataBroker dataService) {
this.dataService = dataService;
}

public void setTimestampUpdateInterval(long timestampUpdateInterval) {
this.timestampUpdateInterval = timestampUpdateInterval;
}

/**
* Add addresses into the MD-SAL data tree.
*
* @param macAddress The MacAddress of the new L2Address object
* @param ipAddress The Source Protocol Address
* @param nodeConnectorRef The NodeConnectorRef of the new L2Address object
*/
public void addAddress(MacAddress macAddress, IpAddress ipAddress, NodeConnectorRef nodeConnectorRef) {
if (macAddress == null || ipAddress == null || nodeConnectorRef == null) {
return;
}

// get the lock for given node connector so at a time only one
// observation can be made on a node connector
NodeConnectorLock nodeConnectorLock = lockMap.computeIfAbsent(nodeConnectorRef, key -> new NodeConnectorLock());

synchronized (nodeConnectorLock) {
// Ensure previous transaction finished writing to the db
FluentFuture<CommitInfo> future = futureMap.get(nodeConnectorLock);
if (future != null) {
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
LOG.error("Exception while waiting for previous transaction to finish", e);
}
}

// Initialize builders
long now = new Date().getTime();
final AddressCapableNodeConnectorBuilder acncBuilder = new AddressCapableNodeConnectorBuilder();
final AddressesBuilder addressBuilder = new AddressesBuilder().setIp(ipAddress).setMac(macAddress)
.setFirstSeen(now).setLastSeen(now);

// Read existing address observations from data tree
final FluentFuture<Optional<NodeConnector>> readFuture;
try (ReadTransaction readTransaction = dataService.newReadOnlyTransaction()) {
readFuture = readTransaction.read(LogicalDatastoreType.OPERATIONAL,
(DataObjectIdentifier<NodeConnector>) nodeConnectorRef.getValue());
}

final NodeConnector nc;
try {
final Optional<NodeConnector> dataObjectOptional = readFuture.get();
if (dataObjectOptional.isEmpty()) {
return;
}
nc = dataObjectOptional.orElseThrow();
} catch (InterruptedException | ExecutionException e) {
LOG.error("Error reading node connector {}", nodeConnectorRef.getValue());
throw new RuntimeException("Error reading from operational store, node connector : " + nodeConnectorRef,
e);
}
AddressCapableNodeConnector acnc = nc.augmentation(AddressCapableNodeConnector.class);

// FIXME: This block is quite inefficient: we have a unique key and yet we end
// up re-merging the entire
// 'addresses' list over and over again -- which is costly due to the HashMap
// copy below as well as
// the resulting merge() operation. After issuing the read, we should determine
// the key of the
// matching entry (or allocate a new key) and issue a simple put() on only the
// key.
final Map<AddressesKey, Addresses> addresses;
if (acnc != null) {
// Address observations exist
addresses = new HashMap<>(acnc.nonnullAddresses());
// Search for this mac-ip pair in the existing address observations & update
// last-seen timestamp
for (Addresses existing : addresses.values()) {
if (ipAddress.equals(existing.getIp()) && macAddress.equals(existing.getMac())) {
if ((now - existing.getLastSeen() <= timestampUpdateInterval)) {
// Update interval has not elapsed, do not run update
return;
}
addressBuilder.setFirstSeen(existing.getFirstSeen()).withKey(existing.key());
break;
}
}
} else {
// Address observations don't exist, so create the list
addresses = new HashMap<>();
}

if (addressBuilder.key() == null) {
addressBuilder.withKey(new AddressesKey(Uint64.fromLongBits(addressKey.getAndIncrement())));
}

// Add as an augmentation
final Addresses address = addressBuilder.build();
addresses.put(address.key(), address);
acncBuilder.setAddresses(addresses);

// build Instance Id for AddressCapableNodeConnector
DataObjectIdentifier<AddressCapableNodeConnector> addressCapableNcInstanceId = ((DataObjectIdentifier<AddressCapableNodeConnector>) nodeConnectorRef
.getValue());
final WriteTransaction writeTransaction = dataService.newWriteOnlyTransaction();
// Update this AddressCapableNodeConnector in the MD-SAL data tree
writeTransaction.merge(LogicalDatastoreType.OPERATIONAL, addressCapableNcInstanceId, acncBuilder.build());

final FluentFuture writeTxResultFuture = writeTransaction.commit();
Futures.addCallback(writeTxResultFuture, new FutureCallback<CommitInfo>() {
@Override
public void onSuccess(CommitInfo notUsed) {
LOG.debug("AddressObservationWriter write successful for tx :{}", writeTransaction.getIdentifier());
}

@Override
public void onFailure(Throwable throwable) {
LOG.error("AddressObservationWriter write transaction {} failed", writeTransaction.getIdentifier(),
throwable.getCause());
}
}, MoreExecutors.directExecutor());
futureMap.put(nodeConnectorLock, writeTxResultFuture);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
import org.opendaylight.yangtools.binding.DataObjectIdentifier;
import org.opendaylight.yangtools.util.concurrent.FluentFutures;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;

Expand All @@ -54,14 +55,14 @@ public void init() throws Exception {
macAddress = new MacAddress("ba:43:52:ce:09:f4");
ipAddress = new IpAddress(new Ipv4Address("10.0.0.1"));
realNcRef = new NodeConnectorRef(
InstanceIdentifier.builder(Nodes.class).child(Node.class).child(NodeConnector.class).build());
DataObjectIdentifier.builder(Nodes.class).child(Node.class).child(NodeConnector.class).build());

readTransaction = mock(ReadTransaction.class);
dataService = mock(DataBroker.class);
nodeConnector = mock(NodeConnector.class);
when(dataService.newReadOnlyTransaction()).thenReturn(readTransaction);
checkedFuture = FluentFutures.immediateFluentFuture(Optional.of(nodeConnector));
when(readTransaction.read(any(LogicalDatastoreType.class), any(InstanceIdentifier.class)))
when(readTransaction.read(any(LogicalDatastoreType.class), any(DataObjectIdentifier.class)))
.thenReturn(checkedFuture);

addrCapableNc = mock(AddressCapableNodeConnector.class);
Expand Down
2 changes: 1 addition & 1 deletion addresstracker/model/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
<configuration>
<instructions>
<Bundle-Name>${project.groupId}.${project.artifactId}</Bundle-Name>
<Import-Package>org.opendaylight.yangtools.yang.binding.annotations, *</Import-Package>
<Import-Package>*</Import-Package>
</instructions>
</configuration>
</plugin>
Expand Down
9 changes: 9 additions & 0 deletions arphandler/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@
<groupId>org.opendaylight.yangtools</groupId>
<artifactId>yang-common</artifactId>
</dependency>
<dependency>
<groupId>org.opendaylight.yangtools</groupId>
<artifactId>binding-spec</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.opendaylight.yangtools</groupId>
<artifactId>binding-spec</artifactId>
</dependency>
</dependencies>

<build>
Expand Down
Loading
Loading