@Override
public void onDeviceDisconnected() {
LOG.info("Device {} disconnected - unregistering master mount point", id);
- datastoreAdapter.updateDeviceData(false, NetconfDeviceCapabilities.empty());
+ datastoreAdapter.updateDeviceData(false, NetconfDeviceCapabilities.empty(), null);
mount.onDeviceDisconnected();
}
private void updateDeviceData() {
final String masterAddress = Cluster.get(actorSystem).selfAddress().toString();
LOG.debug("{}: updateDeviceData with master address {}", id, masterAddress);
- datastoreAdapter.updateClusteredDeviceData(true, masterAddress, currentSchema.capabilities());
+ datastoreAdapter.updateClusteredDeviceData(true, masterAddress, currentSchema.capabilities(),
+ netconfSessionPreferences.sessionId());
}
}
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import java.util.concurrent.ExecutionException;
-import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.mdsal.binding.api.DataBroker;
import org.opendaylight.mdsal.binding.api.Transaction;
import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier;
import org.opendaylight.yangtools.yang.common.Empty;
import org.opendaylight.yangtools.yang.common.Uint16;
+import org.opendaylight.yangtools.yang.common.Uint32;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
return nodePath().augmentation(NetconfNode.class);
}
- public void updateDeviceData(final boolean up, final NetconfDeviceCapabilities capabilities) {
+ public void updateDeviceData(final boolean up, final NetconfDeviceCapabilities capabilities,
+ final Uint32 sessionId) {
final WriteTransaction writeTx = txChain.newWriteOnlyTransaction();
LOG.trace("{}: Update device state transaction {} merging operational data started.",
id, writeTx.getIdentifier());
// FIXME: this needs to be tied together with node's operational existence
writeTx.mergeParentStructurePut(LogicalDatastoreType.OPERATIONAL, netconfNodePath(),
- newNetconfNodeBuilder(up, capabilities).build());
+ newNetconfNodeBuilder(up, capabilities, sessionId).build());
LOG.trace("{}: Update device state transaction {} merging operational data ended.",
id, writeTx.getIdentifier());
}
public void updateClusteredDeviceData(final boolean up, final String masterAddress,
- final NetconfDeviceCapabilities capabilities) {
+ final NetconfDeviceCapabilities capabilities, final Uint32 sessionId) {
final WriteTransaction writeTx = txChain.newWriteOnlyTransaction();
LOG.trace("{}: Update device state transaction {} merging operational data started.",
id, writeTx.getIdentifier());
writeTx.mergeParentStructurePut(LogicalDatastoreType.OPERATIONAL, netconfNodePath(),
- newNetconfNodeBuilder(up, capabilities)
+ newNetconfNodeBuilder(up, capabilities, sessionId)
.setClusteredConnectionStatus(new ClusteredConnectionStatusBuilder()
.setNetconfMasterNode(masterAddress)
.build())
commitTransaction(writeTx, "update-failed-device");
}
- private NetconfNodeBuilder newNetconfNodeBuilder(final boolean up, final NetconfDeviceCapabilities capabilities) {
+ private NetconfNodeBuilder newNetconfNodeBuilder(final boolean up, final NetconfDeviceCapabilities capabilities,
+ final Uint32 sessionId) {
return new NetconfNodeBuilder()
.setHost(id.host())
.setPort(new PortNumber(Uint16.valueOf(id.address().getPort())))
.setCapability(unresolved.getKey().toString())
.setFailureReason(unresolved.getValue())
.build())
- .collect(Collectors.toUnmodifiableList()))
- .build());
+ .toList())
+ .build())
+ .setSessionId(sessionId);
}
private void commitTransaction(final WriteTransaction transaction, final String txType) {
//non-module capabilities should not exist in yang module capabilities
final var sessionPreferences = NetconfSessionPreferences.fromStrings(capabilities,
- CapabilityOrigin.DeviceAdvertised);
+ CapabilityOrigin.DeviceAdvertised, node.getSessionId());
final var nonModulePrefs = sessionPreferences.nonModuleCaps();
if (!nonModulePrefs.isEmpty()) {
throw new IllegalArgumentException("List yang-module-capabilities/capability should contain only module "
// FIXME: UserPreferences is constructor parameter of NetconfDeviceCommunicator and NetconfSessionPreferences
// are created in NetconfDeviceCommunicator#onSessionUp from session. What are we doing here?
// IMO we should rework UserPreferences and NetconfSessionPreferences and this method.
- return new UserPreferences(NetconfSessionPreferences.fromStrings(capabilities, CapabilityOrigin.UserDefined),
- overrideYangModuleCaps, overrideNonModuleCaps);
+ return new UserPreferences(NetconfSessionPreferences.fromStrings(capabilities, CapabilityOrigin.UserDefined,
+ node.getSessionId()), overrideYangModuleCaps, overrideNonModuleCaps);
}
@Deprecated(forRemoval = true)
public synchronized void onDeviceConnected(final NetconfDeviceSchema deviceSchema,
final NetconfSessionPreferences sessionPreferences, final RemoteDeviceServices services) {
super.onDeviceConnected(deviceSchema, sessionPreferences, services);
- datastoreAdapter.updateDeviceData(true, deviceSchema.capabilities());
-
+ datastoreAdapter.updateDeviceData(true, deviceSchema.capabilities(), sessionPreferences.sessionId());
}
@Override
public synchronized void onDeviceDisconnected() {
- datastoreAdapter.updateDeviceData(false, NetconfDeviceCapabilities.empty());
+ datastoreAdapter.updateDeviceData(false, NetconfDeviceCapabilities.empty(), null);
super.onDeviceDisconnected();
}
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeKey;
import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier;
import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.Uint32;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
wtx.put(LogicalDatastoreType.OPERATIONAL, pathToAugmentedLeaf, augmentNode);
wtx.commit().get(5, TimeUnit.SECONDS);
- adapter.updateDeviceData(true, NetconfDeviceCapabilities.empty());
+ adapter.updateDeviceData(true, NetconfDeviceCapabilities.empty(), Uint32.ONE);
assertEquals(Optional.of(dataTestId), domDataBroker.newReadOnlyTransaction()
.read(LogicalDatastoreType.OPERATIONAL, pathToAugmentedLeaf)
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeKey;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier;
+import org.opendaylight.yangtools.yang.common.Uint32;
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class NetconfDeviceTopologyAdapterTest {
@Test
public void testDeviceUpdate() throws Exception {
- adapter.updateDeviceData(true, NetconfDeviceCapabilities.empty());
+ adapter.updateDeviceData(true, NetconfDeviceCapabilities.empty(), Uint32.ONE);
verify(mockChain, times(2)).newWriteOnlyTransaction();
verify(mockTx, times(1)).put(any(LogicalDatastoreType.class), any(InstanceIdentifier.class), any(Node.class));
import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil;
import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev221225.connection.oper.available.capabilities.AvailableCapability.CapabilityOrigin;
import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.Uint32;
import org.opendaylight.yangtools.yang.common.XMLNamespace;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// FIXME: propagate to API with immutable semantics
public record NetconfSessionPreferences(
@NonNull ImmutableMap<String, CapabilityOrigin> nonModuleCaps,
- @NonNull ImmutableMap<QName, CapabilityOrigin> moduleBasedCaps) {
+ @NonNull ImmutableMap<QName, CapabilityOrigin> moduleBasedCaps,
+ @Nullable Uint32 sessionId) {
private static final Logger LOG = LoggerFactory.getLogger(NetconfSessionPreferences.class);
private static final ParameterMatcher MODULE_PARAM = new ParameterMatcher("module=");
private static final ParameterMatcher REVISION_PARAM = new ParameterMatcher("revision=");
}
public static @NonNull NetconfSessionPreferences fromNetconfSession(final NetconfClientSession session) {
- return fromStrings(session.getServerCapabilities());
+ return fromStrings(session.getServerCapabilities(), CapabilityOrigin.DeviceAdvertised,
+ Uint32.valueOf(session.getSessionId()));
}
@VisibleForTesting
public static @NonNull NetconfSessionPreferences fromStrings(final Collection<String> capabilities) {
- // we do not know origin of capabilities from only Strings, so we set it to default value
- return fromStrings(capabilities, CapabilityOrigin.DeviceAdvertised);
+ return fromStrings(capabilities, CapabilityOrigin.DeviceAdvertised, null);
}
public static @NonNull NetconfSessionPreferences fromStrings(final Collection<String> capabilities,
- final CapabilityOrigin capabilityOrigin) {
+ final CapabilityOrigin capabilityOrigin, final Uint32 sessionId) {
final var moduleBasedCaps = new HashMap<QName, CapabilityOrigin>();
final var nonModuleCaps = new HashMap<String, CapabilityOrigin>();
cachedQName(namespace, moduleName), capabilityOrigin);
}
- return new NetconfSessionPreferences(ImmutableMap.copyOf(nonModuleCaps), ImmutableMap.copyOf(moduleBasedCaps));
+ return new NetconfSessionPreferences(ImmutableMap.copyOf(nonModuleCaps), ImmutableMap.copyOf(moduleBasedCaps),
+ sessionId);
}
public @Nullable CapabilityOrigin capabilityOrigin(final QName capability) {
+ netconfSessionModuleCapabilities.moduleBasedCaps.size());
mergedCaps.putAll(moduleBasedCaps);
mergedCaps.putAll(netconfSessionModuleCapabilities.moduleBasedCaps);
- return new NetconfSessionPreferences(nonModuleCaps, ImmutableMap.copyOf(mergedCaps));
+ return new NetconfSessionPreferences(nonModuleCaps, ImmutableMap.copyOf(mergedCaps),
+ netconfSessionModuleCapabilities.sessionId());
}
/**
* @return new instance of preferences with replaced module-based capabilities
*/
public NetconfSessionPreferences replaceModuleCaps(final NetconfSessionPreferences netconfSessionPreferences) {
- return new NetconfSessionPreferences(nonModuleCaps, netconfSessionPreferences.moduleBasedCaps);
+ return new NetconfSessionPreferences(nonModuleCaps, netconfSessionPreferences.moduleBasedCaps,
+ netconfSessionPreferences.sessionId());
}
public NetconfSessionPreferences replaceModuleCaps(final Map<QName, CapabilityOrigin> newModuleBasedCaps) {
- return new NetconfSessionPreferences(nonModuleCaps, ImmutableMap.copyOf(newModuleBasedCaps));
+ return new NetconfSessionPreferences(nonModuleCaps, ImmutableMap.copyOf(newModuleBasedCaps), sessionId());
}
/**
nonModuleCaps.size() + netconfSessionNonModuleCapabilities.nonModuleCaps.size());
mergedCaps.putAll(nonModuleCaps);
mergedCaps.putAll(netconfSessionNonModuleCapabilities.nonModuleCaps);
- return new NetconfSessionPreferences(ImmutableMap.copyOf(mergedCaps), moduleBasedCaps);
+ return new NetconfSessionPreferences(ImmutableMap.copyOf(mergedCaps), moduleBasedCaps,
+ netconfSessionNonModuleCapabilities.sessionId());
}
/**
* @return new instance of preferences with replaced non-module based capabilities
*/
public NetconfSessionPreferences replaceNonModuleCaps(final NetconfSessionPreferences netconfSessionPreferences) {
- return new NetconfSessionPreferences(netconfSessionPreferences.nonModuleCaps, moduleBasedCaps);
+ return new NetconfSessionPreferences(netconfSessionPreferences.nonModuleCaps, moduleBasedCaps,
+ netconfSessionPreferences.sessionId());
}
private static QName cachedQName(final String namespace, final String revision, final String moduleName) {
}
grouping connection-oper {
+ leaf session-id {
+ config false;
+ type uint32 {
+ range "1..max";
+ }
+ }
+
leaf connection-status {
config false;
type enumeration {
import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import com.google.common.util.concurrent.ListenableFuture;
+import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import java.io.ByteArrayInputStream;
import java.net.InetSocketAddress;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
import org.opendaylight.netconf.client.NetconfClientDispatcherImpl;
import org.opendaylight.netconf.client.NetconfClientSession;
+import org.opendaylight.netconf.client.NetconfClientSessionListener;
import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfiguration;
import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfigurationBuilder;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.RpcError;
import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.common.Uint32;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
public class NetconfDeviceCommunicatorTest {
private static final Logger LOG = LoggerFactory.getLogger(NetconfDeviceCommunicatorTest.class);
-
- @Mock
- NetconfClientSession mockSession;
+ private static final Uint32 SESSION_ID = Uint32.ONE;
@Mock
RemoteDevice<NetconfDeviceCommunicator> mockDevice;
- NetconfDeviceCommunicator communicator;
+ private NetconfClientSession spySession;
+ private NetconfDeviceCommunicator communicator;
@Before
public void setUp() throws Exception {
communicator = new NetconfDeviceCommunicator(
new RemoteDeviceId("test", InetSocketAddress.createUnresolved("localhost", 22)), mockDevice, 10);
+ spySession = spy(new NetconfClientSession(mock(NetconfClientSessionListener.class), mock(Channel.class),
+ SESSION_ID.toJava(), Set.of()));
}
void setupSession() {
- doReturn(Collections.<String>emptySet()).when(mockSession).getServerCapabilities();
doNothing().when(mockDevice).onRemoteSessionUp(any(NetconfSessionPreferences.class),
any(NetconfDeviceCommunicator.class));
- communicator.onSessionUp(mockSession);
+ communicator.onSessionUp(spySession);
}
private ListenableFuture<RpcResult<NetconfMessage>> sendRequest() throws Exception {
ChannelFuture mockChannelFuture = mock(ChannelFuture.class);
doReturn(mockChannelFuture).when(mockChannelFuture)
.addListener(any(GenericFutureListener.class));
- doReturn(mockChannelFuture).when(mockSession).sendMessage(same(message));
+ doReturn(mockChannelFuture).when(spySession).sendMessage(same(message));
ListenableFuture<RpcResult<NetconfMessage>> resultFuture =
communicator.sendRequest(message, QName.create("", "mockRpc"));
NetconfMessageTransformUtil.NETCONF_ROLLBACK_ON_ERROR_URI.toString(),
NetconfMessageTransformUtil.IETF_NETCONF_MONITORING.getNamespace().toString(),
testCapability);
- doReturn(serverCapabilities).when(mockSession).getServerCapabilities();
+ doReturn(serverCapabilities).when(spySession).getServerCapabilities();
final var netconfSessionPreferences = ArgumentCaptor.forClass(NetconfSessionPreferences.class);
doNothing().when(mockDevice).onRemoteSessionUp(netconfSessionPreferences.capture(), eq(communicator));
- communicator.onSessionUp(mockSession);
+ communicator.onSessionUp(spySession);
- verify(mockSession).getServerCapabilities();
+ verify(spySession).getServerCapabilities();
verify(mockDevice).onRemoteSessionUp(netconfSessionPreferences.capture(), eq(communicator));
NetconfSessionPreferences actualCapabilites = netconfSessionPreferences.getValue();
actualCapabilites.moduleBasedCaps().keySet());
assertTrue(actualCapabilites.isRollbackSupported());
assertTrue(actualCapabilites.isMonitoringSupported());
+ assertEquals(SESSION_ID, actualCapabilites.sessionId());
}
@SuppressWarnings("unchecked")
doNothing().when(mockDevice).onRemoteSessionDown();
- communicator.onSessionDown(mockSession, new Exception("mock ex"));
+ communicator.onSessionDown(spySession, new Exception("mock ex"));
verifyErrorRpcResult(resultFuture1.get(), ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
verifyErrorRpcResult(resultFuture2.get(), ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
reset(mockDevice);
- communicator.onSessionDown(mockSession, new Exception("mock ex"));
+ communicator.onSessionDown(spySession, new Exception("mock ex"));
verify(mockDevice, never()).onRemoteSessionDown();
}
String reasonText = "testing terminate";
NetconfTerminationReason reason = new NetconfTerminationReason(reasonText);
- communicator.onSessionTerminated(mockSession, reason);
+ communicator.onSessionTerminated(spySession, reason);
RpcError rpcError = verifyErrorRpcResult(resultFuture.get(), ErrorType.TRANSPORT, ErrorTag.OPERATION_FAILED);
assertEquals("RpcError message", reasonText, rpcError.getMessage());
ChannelFuture mockChannelFuture = mock(ChannelFuture.class);
doReturn(mockChannelFuture).when(mockChannelFuture).addListener(futureListener.capture());
- doReturn(mockChannelFuture).when(mockSession).sendMessage(same(message));
+ doReturn(mockChannelFuture).when(spySession).sendMessage(same(message));
ListenableFuture<RpcResult<NetconfMessage>> resultFuture = communicator.sendRequest(message, rpc);
- verify(mockSession).sendMessage(same(message));
+ verify(spySession).sendMessage(same(message));
assertNotNull("ListenableFuture is null", resultFuture);
ChannelFuture mockChannelFuture = mock(ChannelFuture.class);
doReturn(mockChannelFuture).when(mockChannelFuture).addListener(futureListener.capture());
- doReturn(mockChannelFuture).when(mockSession).sendMessage(same(message));
+ doReturn(mockChannelFuture).when(spySession).sendMessage(same(message));
ListenableFuture<RpcResult<NetconfMessage>> resultFuture = communicator.sendRequest(message, rpc);
ListenableFuture<RpcResult<NetconfMessage>> resultFuture3 = sendRequest(messageID3, true);
//response messages 1,2 are omitted
- communicator.onMessage(mockSession, createSuccessResponseMessage(messageID3));
+ communicator.onMessage(spySession, createSuccessResponseMessage(messageID3));
verifyResponseMessage(resultFuture3.get(), messageID3);
}
String messageID2 = UUID.randomUUID().toString();
final ListenableFuture<RpcResult<NetconfMessage>> resultFuture2 = sendRequest(messageID2, true);
- communicator.onMessage(mockSession, createSuccessResponseMessage(messageID1));
- communicator.onMessage(mockSession, createSuccessResponseMessage(messageID2));
+ communicator.onMessage(spySession, createSuccessResponseMessage(messageID1));
+ communicator.onMessage(spySession, createSuccessResponseMessage(messageID2));
verifyResponseMessage(resultFuture1.get(), messageID1);
verifyResponseMessage(resultFuture2.get(), messageID2);
String messageID = UUID.randomUUID().toString();
ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest(messageID, true);
- communicator.onMessage(mockSession, createErrorResponseMessage(messageID));
+ communicator.onMessage(spySession, createErrorResponseMessage(messageID));
RpcError rpcError = verifyErrorRpcResult(resultFuture.get(), ErrorType.RPC, ErrorTag.MISSING_ATTRIBUTE);
assertEquals("RpcError message", "Missing attribute", rpcError.getMessage());
String messageID = UUID.randomUUID().toString();
ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest(messageID, true);
- communicator.onMessage(mockSession, createMultiErrorResponseMessage(messageID));
+ communicator.onMessage(spySession, createMultiErrorResponseMessage(messageID));
RpcError rpcError = verifyErrorRpcResult(resultFuture.get(), ErrorType.PROTOCOL, ErrorTag.OPERATION_FAILED);
String messageID = UUID.randomUUID().toString();
ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest(messageID, true);
- communicator.onMessage(mockSession, createSuccessResponseMessage(UUID.randomUUID().toString()));
+ communicator.onMessage(spySession, createSuccessResponseMessage(UUID.randomUUID().toString()));
RpcError rpcError = verifyErrorRpcResult(resultFuture.get(), ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE);
assertFalse("RpcError message non-empty", Strings.isNullOrEmpty(rpcError.getMessage()));
ListenableFuture<RpcResult<NetconfMessage>> resultFuture = sendRequest(notWorkingMessageID, false);
assertEquals("ListenableFuture is null", false, resultFuture instanceof UncancellableFuture);
- communicator.onMessage(mockSession, createSuccessResponseMessage(messageID.get(0)));
+ communicator.onMessage(spySession, createSuccessResponseMessage(messageID.get(0)));
resultFuture = sendRequest(messageID.get(0), false);
assertNotNull("ListenableFuture is null", resultFuture);