<artifactId>sal-netconf-connector</artifactId>
<version>${mdsal.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-distributed-datastore</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-module-junit4</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-api-mockito</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-core</artifactId>
+ </dependency>
</dependencies>
</project>
\ No newline at end of file
if (Strings.isNullOrEmpty(devicePort)) {
return false;
}
- Integer port = Integer.parseInt(devicePort);
- if (port != null && port >= 0 && port <= 65535) {
- return true;
+ Integer port;
+ try {
+ port = Integer.parseInt(devicePort);
+ } catch (NumberFormatException e) {
+ return false;
}
- return false;
+ return port >= 0 && port <= 65535;
}
public static boolean isIpValid(final String deviceIp) {
package org.opendaylight.netconf.console.commands;
+import com.google.common.annotations.VisibleForTesting;
import org.apache.karaf.shell.commands.Command;
import org.apache.karaf.shell.commands.Option;
import org.apache.karaf.shell.console.AbstractAction;
this.service = service;
}
+ @VisibleForTesting
+ NetconfConnectDeviceCommand(final NetconfCommands service, final String deviceIp, final String devicePort) {
+ this.service = service;
+ this.deviceIp = deviceIp;
+ this.devicePort = devicePort;
+ }
+
@Option(name = "-i",
aliases = { "--ipaddress" },
description = "IP address of the netconf device",
package org.opendaylight.netconf.console.commands;
+import com.google.common.annotations.VisibleForTesting;
import org.apache.karaf.shell.commands.Command;
import org.apache.karaf.shell.commands.Option;
import org.apache.karaf.shell.console.AbstractAction;
this.service = service;
}
+ @VisibleForTesting
+ NetconfDisconnectDeviceCommand(final NetconfCommands service, final String deviceId, final String deviceIp,
+ final String devicePort) {
+ this.service = service;
+ this.deviceId = deviceId;
+ this.deviceIp = deviceIp;
+ this.devicePort = devicePort;
+ }
+
@Option(name = "-i",
aliases = { "--ipaddress" },
description = "IP address of the netconf device",
package org.opendaylight.netconf.console.commands;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import java.util.List;
import java.util.Map;
this.service = service;
}
+ @VisibleForTesting
+ NetconfShowDeviceCommand(final NetconfCommands service, final String deviceId, final String deviceIp,
+ final String devicePort) {
+ this.service = service;
+ this.deviceId = deviceId;
+ this.deviceIp = deviceIp;
+ this.devicePort = devicePort;
+ }
+
@Option(name = "-id",
aliases = { "--identifier" },
description = "Node Identifier of the netconf device",
package org.opendaylight.netconf.console.commands;
+import com.google.common.annotations.VisibleForTesting;
import java.util.HashMap;
import java.util.Map;
this.service = service;
}
+ @VisibleForTesting
+ NetconfUpdateDeviceCommand(final NetconfCommands service, final String newIp) {
+ this.service = service;
+ this.newIp = newIp;
+ }
+
@Option(name = "-id",
aliases = { "--nodeId" },
description = "NETCONF node ID of the netconf device",
--- /dev/null
+/*
+ * Copyright (c) 2016 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.netconf.console.commands;
+
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertTrue;
+
+import org.junit.Test;
+
+public class NetconfCommandUtilsTest {
+
+ @Test
+ public void testIsPortValid() {
+ final boolean portTrue = NetconfCommandUtils.isPortValid("65535");
+ final boolean portTrue2 = NetconfCommandUtils.isPortValid("0");
+
+ final boolean portFalse = NetconfCommandUtils.isPortValid("123x");
+ final boolean portFalse2 = NetconfCommandUtils.isPortValid("65536");
+ final boolean portFalse3 = NetconfCommandUtils.isPortValid("");
+
+ assertTrue(portTrue);
+ assertTrue(portTrue2);
+ assertFalse(portFalse);
+ assertFalse(portFalse2);
+ assertFalse(portFalse3);
+
+ }
+
+ @Test
+ public void testIsIpValid() {
+ final boolean ipTrue = NetconfCommandUtils.isIpValid("0.0.0.0");
+ final boolean ipTrue2 = NetconfCommandUtils.isIpValid("255.255.255.255");
+
+ final boolean ipFalse = NetconfCommandUtils.isIpValid("256.1.1.1");
+ final boolean ipFalse2 = NetconfCommandUtils.isIpValid("123.145.12.x");
+ final boolean ipFalse3 = NetconfCommandUtils.isIpValid("");
+
+ assertTrue(ipTrue);
+ assertTrue(ipTrue2);
+ assertFalse(ipFalse);
+ assertFalse(ipFalse2);
+ assertFalse(ipFalse3);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 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.netconf.console.commands;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.google.common.collect.Lists;
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.opendaylight.netconf.console.api.NetconfCommands;
+import org.opendaylight.netconf.console.utils.NetconfConsoleConstants;
+
+public class NetconfCommandsImplCallsTest {
+
+ @Mock
+ private NetconfCommands netconfCommands;
+
+ @Before
+ public void setUp(){
+ initMocks(this);
+ }
+
+ @Test
+ public void testConnectDeviceCommand() throws Exception {
+ NetconfConnectDeviceCommand netconfConnectDeviceCommand =
+ new NetconfConnectDeviceCommand(netconfCommands);
+ netconfConnectDeviceCommand.doExecute();
+ verify(netconfCommands, times(0)).connectDevice(any(), any());
+
+ netconfConnectDeviceCommand = new NetconfConnectDeviceCommand(netconfCommands, "192.168.1.1", "7777");
+
+ netconfConnectDeviceCommand.doExecute();
+ doNothing().when(netconfCommands).connectDevice(any(), any());
+ verify(netconfCommands, times(1)).connectDevice(any(), any());
+ }
+
+ @Test
+ public void testDisconnectDeviceCommand() throws Exception {
+ NetconfDisconnectDeviceCommand netconfDisconnectDeviceCommand = new NetconfDisconnectDeviceCommand(netconfCommands);
+ netconfDisconnectDeviceCommand.doExecute();
+
+ verify(netconfCommands, times(0)).connectDevice(any(), any());
+
+ netconfDisconnectDeviceCommand = new NetconfDisconnectDeviceCommand(netconfCommands, "deviceId", null, null);
+
+ doReturn(true).when(netconfCommands).disconnectDevice(any());
+ netconfDisconnectDeviceCommand.doExecute();
+
+ verify(netconfCommands, times(1)).disconnectDevice(any());
+
+ netconfDisconnectDeviceCommand =
+ new NetconfDisconnectDeviceCommand(netconfCommands, null, "192.168.1.1", "7777");
+
+ doReturn(true).when(netconfCommands).disconnectDevice(any(), any());
+ netconfDisconnectDeviceCommand.doExecute();
+
+ verify(netconfCommands, times(1)).disconnectDevice(any(), any());
+ }
+
+ @Test
+ public void testListDeviceCommand() throws Exception {
+ final NetconfListDevicesCommand netconfListDeviceCommand = new NetconfListDevicesCommand(netconfCommands);
+ doReturn(getDeviceHashMap()).when(netconfCommands).listDevices();
+
+ netconfListDeviceCommand.doExecute();
+
+ verify(netconfCommands, times(1)).listDevices();
+ }
+
+ @Test
+ public void testShowDeviceCommand() throws Exception {
+ NetconfShowDeviceCommand netconfShowDeviceCommand = new NetconfShowDeviceCommand(netconfCommands);
+ netconfShowDeviceCommand.doExecute();
+
+ verify(netconfCommands, times(0)).showDevice(any());
+
+ netconfShowDeviceCommand = new NetconfShowDeviceCommand(netconfCommands, "deviceId", null, null);
+
+ doReturn(getDeviceHashMap()).when(netconfCommands).showDevice(any());
+ netconfShowDeviceCommand.doExecute();
+
+ verify(netconfCommands, times(1)).showDevice(any());
+
+ netconfShowDeviceCommand = new NetconfShowDeviceCommand(netconfCommands, null, "192.168.1.1", "7777");
+
+ doReturn(getDeviceHashMap()).when(netconfCommands).showDevice(any(), any());
+ netconfShowDeviceCommand.doExecute();
+
+ verify(netconfCommands, times(1)).showDevice(any(), any());
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testUpdateDeviceCommand() throws Exception {
+ final NetconfUpdateDeviceCommand netconfUpdateDeviceCommand =
+ new NetconfUpdateDeviceCommand(netconfCommands, "192.168.1.1");
+
+ final ArgumentCaptor<HashMap> hashMapArgumentCaptor = ArgumentCaptor.forClass(HashMap.class);
+
+ doReturn("").when(netconfCommands).updateDevice(anyString(), anyString(), anyString(), any());
+
+ netconfUpdateDeviceCommand.doExecute();
+
+ verify(netconfCommands, times(1)).updateDevice(anyString(), anyString(), anyString(), hashMapArgumentCaptor.capture());
+
+ assertTrue(hashMapArgumentCaptor.getValue().containsKey(NetconfConsoleConstants.NETCONF_IP));
+ assertEquals("192.168.1.1", hashMapArgumentCaptor.getValue().get(NetconfConsoleConstants.NETCONF_IP));
+ }
+
+ private HashMap getDeviceHashMap() {
+ final HashMap<String, Map<String, List<String>>> devices = new HashMap<>();
+ final HashMap<String, List<String>> deviceMap = new HashMap<>();
+ deviceMap.put(NetconfConsoleConstants.NETCONF_IP, Lists.newArrayList("192.168.1.1"));
+ deviceMap.put(NetconfConsoleConstants.NETCONF_PORT, Lists.newArrayList("7777"));
+ deviceMap.put(NetconfConsoleConstants.STATUS, Lists.newArrayList("connecting"));
+ deviceMap.put(NetconfConsoleConstants.AVAILABLE_CAPABILITIES, Lists.newArrayList("cap1", "cap2", "cap3"));
+ devices.put("device", deviceMap);
+ return devices;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 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.netconf.console.impl;
+
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertNull;
+import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.mock;
+
+import com.google.common.collect.ImmutableList;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import javassist.ClassPool;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.cluster.databroker.ConcurrentDOMDataBroker;
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.binding.api.MountPointService;
+import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
+import org.opendaylight.controller.md.sal.binding.impl.BindingDOMDataBrokerAdapter;
+import org.opendaylight.controller.md.sal.binding.impl.BindingToNormalizedNodeCodec;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.controller.md.sal.dom.store.impl.InMemoryDOMDataStoreFactory;
+import org.opendaylight.controller.sal.core.api.model.SchemaService;
+import org.opendaylight.controller.sal.core.spi.data.DOMStore;
+import org.opendaylight.netconf.console.utils.NetconfConsoleConstants;
+import org.opendaylight.netconf.console.utils.NetconfConsoleUtils;
+import org.opendaylight.netconf.console.utils.NetconfIidFactory;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Host;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.HostBuilder;
+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.netconf.node.topology.rev150114.NetconfNode;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionStatus;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.AvailableCapabilities;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.AvailableCapabilitiesBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.network.topology.topology.topology.types.TopologyNetconf;
+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.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.TopologyBuilder;
+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.NodeBuilder;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeKey;
+import org.opendaylight.yangtools.binding.data.codec.gen.impl.DataObjectSerializerGenerator;
+import org.opendaylight.yangtools.binding.data.codec.gen.impl.StreamWriterGenerator;
+import org.opendaylight.yangtools.binding.data.codec.impl.BindingNormalizedNodeCodecRegistry;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.sal.binding.generator.impl.GeneratedClassLoadingStrategy;
+import org.opendaylight.yangtools.sal.binding.generator.impl.ModuleInfoBackedContext;
+import org.opendaylight.yangtools.sal.binding.generator.util.BindingRuntimeContext;
+import org.opendaylight.yangtools.sal.binding.generator.util.JavassistUtils;
+import org.opendaylight.yangtools.util.concurrent.SpecialExecutors;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
+import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
+import org.opendaylight.yangtools.yang.parser.stmt.reactor.CrossSourceStatementReactor;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangInferencePipeline;
+
+public class NetconfCommandsImplTest {
+
+ private static final String NODE_ID = "NodeID";
+ private static final String IP = "192.168.1.1";
+ private static final int PORT = 1234;
+ private static final NetconfNodeConnectionStatus.ConnectionStatus CONN_STATUS =
+ NetconfNodeConnectionStatus.ConnectionStatus.Connected;
+ private static final String CAP_PREFIX = "prefix";
+
+ private DataBroker dataBroker;
+ private SchemaContext schemaContext;
+ private NetconfCommandsImpl netconfCommands;
+
+ @Before
+ public void setUp() throws TransactionCommitFailedException, TimeoutException, InterruptedException {
+ schemaContext = parseYangStreams(getYangSchemas());
+ schemaContext.getModules();
+ final SchemaService schemaService = createSchemaService();
+
+ final DOMStore operStore = InMemoryDOMDataStoreFactory.create("DOM-OPER", schemaService);
+ final DOMStore configStore = InMemoryDOMDataStoreFactory.create("DOM-CFG", schemaService);
+
+ final EnumMap<LogicalDatastoreType, DOMStore> datastores = new EnumMap<>(LogicalDatastoreType.class);
+ datastores.put(LogicalDatastoreType.CONFIGURATION, configStore);
+ datastores.put(LogicalDatastoreType.OPERATIONAL, operStore);
+
+ final ExecutorService listenableFutureExecutor = SpecialExecutors.newBlockingBoundedCachedThreadPool(
+ 16, 16, "CommitFutures");
+
+ final ConcurrentDOMDataBroker cDOMDataBroker = new ConcurrentDOMDataBroker(datastores, listenableFutureExecutor);
+
+ final ClassPool pool = ClassPool.getDefault();
+ final DataObjectSerializerGenerator generator = StreamWriterGenerator.create(JavassistUtils.forClassPool(pool));
+ final BindingNormalizedNodeCodecRegistry codecRegistry = new BindingNormalizedNodeCodecRegistry(generator);
+ final ModuleInfoBackedContext moduleInfoBackedContext = ModuleInfoBackedContext.create();
+ codecRegistry.onBindingRuntimeContextUpdated(BindingRuntimeContext.create(moduleInfoBackedContext, schemaContext));
+
+ final GeneratedClassLoadingStrategy loading = GeneratedClassLoadingStrategy.getTCCLClassLoadingStrategy();
+ final BindingToNormalizedNodeCodec bindingToNormalized = new BindingToNormalizedNodeCodec(loading, codecRegistry);
+ bindingToNormalized.onGlobalContextUpdated(schemaContext);
+ dataBroker = new BindingDOMDataBrokerAdapter(cDOMDataBroker, bindingToNormalized);
+
+ final MountPointService mountPointService = mock(MountPointService.class);
+ netconfCommands = new NetconfCommandsImpl(dataBroker, mountPointService);
+ }
+
+ @Test
+ public void testListDevice() throws TimeoutException, TransactionCommitFailedException {
+ createTopology(LogicalDatastoreType.OPERATIONAL);
+
+ final Map map = netconfCommands.listDevices();
+ map.containsKey(NetconfConsoleConstants.NETCONF_ID);
+ assertTrue(map.containsKey(NODE_ID));
+
+ final Map mapNode = (Map) map.get(NODE_ID);
+ assertBaseNodeAttributes(mapNode);
+ }
+
+ @Test
+ public void testShowDevice() throws TimeoutException, TransactionCommitFailedException {
+ createTopology(LogicalDatastoreType.OPERATIONAL);
+
+ final Map mapCorrect = netconfCommands.showDevice(IP, String.valueOf(PORT));
+ mapCorrect.containsKey(NetconfConsoleConstants.NETCONF_ID);
+ assertTrue(mapCorrect.containsKey(NODE_ID));
+
+ assertBaseNodeAttributesImmutableList((Map) mapCorrect.get(NODE_ID));
+
+ final Map mapWrongPort = netconfCommands.showDevice(IP, "1");
+ assertFalse(mapWrongPort.containsKey(NODE_ID));
+
+ final Map mapWrongIP = netconfCommands.showDevice("1.1.1.1", String.valueOf(PORT));
+ assertFalse(mapWrongIP.containsKey(NODE_ID));
+
+ final Map mapId = netconfCommands.showDevice(NODE_ID);
+ assertTrue(mapId.containsKey(NODE_ID));
+ assertBaseNodeAttributesImmutableList((Map) mapId.get(NODE_ID));
+ }
+
+ @Test
+ public void testConnectDisconnectDevice() throws InterruptedException, TimeoutException, TransactionCommitFailedException {
+ final NetconfNode netconfNode = new NetconfNodeBuilder().setPort(new PortNumber(7777)).
+ setHost(HostBuilder.getDefaultInstance("10.10.1.1")).build();
+
+ createTopology(LogicalDatastoreType.CONFIGURATION);
+ netconfCommands.connectDevice(netconfNode, "netconf-ID");
+ NetconfConsoleUtils.waitForUpdate("10.10.1.1");
+
+ final Topology topology = NetconfConsoleUtils.read(LogicalDatastoreType.CONFIGURATION,
+ NetconfIidFactory.NETCONF_TOPOLOGY_IID, dataBroker);
+ final List<Node> nodes = topology.getNode();
+ assertEquals(2, nodes.size());
+
+ final Optional<Node> storedNode = nodes.stream().filter(node ->
+ node.getKey().getNodeId().getValue().equals("netconf-ID")).findFirst();
+
+ assertTrue(storedNode.isPresent());
+
+ NetconfNode storedNetconfNode = storedNode.get().getAugmentation(NetconfNode.class);
+ assertEquals(7777, storedNetconfNode.getPort().getValue().longValue());
+ assertEquals("10.10.1.1", storedNetconfNode.getHost().getIpAddress().getIpv4Address().getValue());
+
+ netconfCommands.disconnectDevice("netconf-ID");
+
+ final Topology topologyDeleted = NetconfConsoleUtils.read(LogicalDatastoreType.CONFIGURATION,
+ NetconfIidFactory.NETCONF_TOPOLOGY_IID, dataBroker);
+ final List<Node> nodesDeleted = topologyDeleted.getNode();
+ assertEquals(1, nodesDeleted.size());
+
+ final Optional<Node> storedNodeDeleted = nodesDeleted.stream().filter(node ->
+ node.getKey().getNodeId().getValue().equals("netconf-ID")).findFirst();
+
+ assertFalse(storedNodeDeleted.isPresent());
+ }
+
+ @Test
+ public void testUpdateDevice() throws TimeoutException, TransactionCommitFailedException {
+ //We need both, read data from OPERATIONAL DS and update data in CONFIGURATIONAL DS
+ createTopology(LogicalDatastoreType.OPERATIONAL);
+ createTopology(LogicalDatastoreType.CONFIGURATION);
+
+ final Map<String, String> update = new HashMap<>();
+ update.put(NetconfConsoleConstants.NETCONF_IP, "7.7.7.7");
+ update.put(NetconfConsoleConstants.TCP_ONLY, "true");
+
+ netconfCommands.updateDevice(NODE_ID, "admin", "admin", update);
+ NetconfConsoleUtils.waitForUpdate("7.7.7.7");
+
+ final Topology topology = NetconfConsoleUtils.read(LogicalDatastoreType.CONFIGURATION,
+ NetconfIidFactory.NETCONF_TOPOLOGY_IID, dataBroker);
+ final List<Node> nodes = topology.getNode();
+ assertEquals(1, nodes.size());
+
+ final Optional<Node> storedNode = nodes.stream().filter(node ->
+ node.getKey().getNodeId().getValue().equals(NODE_ID)).findFirst();
+ assertTrue(storedNode.isPresent());
+
+ NetconfNode storedNetconfNode = storedNode.get().getAugmentation(NetconfNode.class);
+ assertEquals("7.7.7.7", storedNetconfNode.getHost().getIpAddress().getIpv4Address().getValue());
+ }
+
+ @Test
+ public void testNetconfNodeFromIp() throws TimeoutException, TransactionCommitFailedException {
+ final List<Node> nodesNotExist = NetconfConsoleUtils.getNetconfNodeFromIp(IP, dataBroker);
+ assertNull(nodesNotExist);
+ createTopology(LogicalDatastoreType.OPERATIONAL);
+ final List<Node> nodes = NetconfConsoleUtils.getNetconfNodeFromIp(IP, dataBroker);
+ assertNotNull(nodes);
+ assertEquals(1, nodes.size());
+ }
+
+ private void createTopology(LogicalDatastoreType dataStoreType) throws TransactionCommitFailedException, TimeoutException {
+ final List<Node> nodes = new ArrayList<>();
+ final Node node = getNetconfNode(NODE_ID, IP, PORT, CONN_STATUS, CAP_PREFIX);
+ nodes.add(node);
+
+ final Topology topology = new TopologyBuilder().
+ setKey(new TopologyKey(new TopologyId(TopologyNetconf.QNAME.getLocalName()))).
+ setTopologyId(new TopologyId(TopologyNetconf.QNAME.getLocalName())).setNode(nodes).build();
+
+ final WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
+ writeTransaction.put(dataStoreType, NetconfIidFactory.NETCONF_TOPOLOGY_IID, topology);
+ writeTransaction.submit().checkedGet(2, TimeUnit.SECONDS);
+ }
+
+ private Node getNetconfNode(String nodeIdent, String ip, int portNumber, NetconfNodeConnectionStatus.ConnectionStatus cs,
+ String notificationCapabilityPrefix) {
+
+ final Host host = HostBuilder.getDefaultInstance(ip);
+ final PortNumber port = new PortNumber(portNumber);
+
+ final List<String> avCapList = new ArrayList<>();
+ avCapList.add(notificationCapabilityPrefix + "_availableCapabilityString1");
+ final AvailableCapabilities avCaps = new AvailableCapabilitiesBuilder().setAvailableCapability(avCapList).build();
+
+ final NetconfNode nn = new NetconfNodeBuilder().setConnectionStatus(cs).setHost(host).setPort(port).
+ setAvailableCapabilities(avCaps).build();
+ final NodeId nodeId = new NodeId(nodeIdent);
+ final NodeKey nk = new NodeKey(nodeId);
+ final NodeBuilder nb = new NodeBuilder();
+ nb.setKey(nk);
+ nb.setNodeId(nodeId);
+ nb.addAugmentation(NetconfNode.class, nn);
+ return nb.build();
+ }
+
+ private void assertBaseNodeAttributes(Map mapNode) {
+
+ assertTrue(mapNode.containsKey(NetconfConsoleConstants.NETCONF_ID));
+ assertTrue(mapNode.containsKey(NetconfConsoleConstants.NETCONF_IP));
+ assertTrue(mapNode.containsKey(NetconfConsoleConstants.NETCONF_PORT));
+ assertTrue(mapNode.containsKey(NetconfConsoleConstants.STATUS));
+
+ assertEquals(NODE_ID, mapNode.get(NetconfConsoleConstants.NETCONF_ID));
+ assertEquals(IP, mapNode.get(NetconfConsoleConstants.NETCONF_IP));
+ assertEquals(String.valueOf(PORT), mapNode.get(NetconfConsoleConstants.NETCONF_PORT));
+ assertEquals(CONN_STATUS.name().toLowerCase(), mapNode.get(NetconfConsoleConstants.STATUS));
+ }
+
+ private void assertBaseNodeAttributesImmutableList(Map mapNode) {
+ assertTrue(mapNode.containsKey(NetconfConsoleConstants.NETCONF_ID));
+ assertTrue(mapNode.containsKey(NetconfConsoleConstants.NETCONF_IP));
+ assertTrue(mapNode.containsKey(NetconfConsoleConstants.NETCONF_PORT));
+ assertTrue(mapNode.containsKey(NetconfConsoleConstants.STATUS));
+
+ assertEquals(ImmutableList.of(NODE_ID), mapNode.get(NetconfConsoleConstants.NETCONF_ID));
+ assertEquals(ImmutableList.of(IP), mapNode.get(NetconfConsoleConstants.NETCONF_IP));
+ assertEquals(ImmutableList.of(String.valueOf(PORT)), mapNode.get(NetconfConsoleConstants.NETCONF_PORT));
+ assertEquals(ImmutableList.of(CONN_STATUS.name()), mapNode.get(NetconfConsoleConstants.STATUS));
+ }
+
+ private List<InputStream> getYangSchemas() {
+ final List<String> schemaPaths = Arrays.asList("/schemas/network-topology@2013-10-21.yang",
+ "/schemas/ietf-inet-types@2013-07-15.yang", "/schemas/yang-ext.yang", "/schemas/netconf-node-topology.yang");
+ final List<InputStream> schemas = new ArrayList<>();
+ for (String schemaPath : schemaPaths) {
+ final InputStream resourceAsStream = getClass().getResourceAsStream(schemaPath);
+ schemas.add(resourceAsStream);
+ }
+ return schemas;
+ }
+
+ private static SchemaContext parseYangStreams(final List<InputStream> streams) {
+ CrossSourceStatementReactor.BuildAction reactor = YangInferencePipeline.RFC6020_REACTOR
+ .newBuild();
+ final SchemaContext schemaContext;
+ try {
+ schemaContext = reactor.buildEffective(streams);
+ } catch (ReactorException e) {
+ throw new RuntimeException("Unable to build schema context from " + streams, e);
+ }
+ return schemaContext;
+ }
+
+ private SchemaService createSchemaService() {
+ return new SchemaService() {
+
+ @Override
+ public void addModule(Module module) {
+ }
+
+ @Override
+ public void removeModule(Module module) {
+
+ }
+
+ @Override
+ public SchemaContext getSessionContext() {
+ return schemaContext;
+ }
+
+ @Override
+ public SchemaContext getGlobalContext() {
+ return schemaContext;
+ }
+
+ @Override
+ public ListenerRegistration<SchemaContextListener> registerSchemaContextListener(final SchemaContextListener listener) {
+ listener.onGlobalContextUpdated(getGlobalContext());
+ return new ListenerRegistration<SchemaContextListener>() {
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public SchemaContextListener getInstance() {
+ return listener;
+ }
+ };
+ }
+ };
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 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.netconf.console.impl;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.powermock.api.mockito.PowerMockito.when;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.BDDMockito;
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.binding.api.MountPointService;
+import org.opendaylight.controller.sal.binding.api.BindingAwareBroker;
+import org.opendaylight.netconf.console.api.NetconfCommands;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(FrameworkUtil.class)
+public class NetconfConsoleProviderTest {
+
+ @Test
+ public void testProvider() throws Exception {
+ final NetconfConsoleProvider netconfConsoleProvider = new NetconfConsoleProvider();
+
+ PowerMockito.mockStatic(FrameworkUtil.class);
+
+ final BindingAwareBroker.ProviderContext session = mock(BindingAwareBroker.ProviderContext.class);
+ final MountPointService mountPointService = mock(MountPointService.class);
+ final BundleContext bundleContext = mock(BundleContext.class);
+ final DataBroker dataBroker = mock(DataBroker.class);
+ final Bundle bundle = mock(Bundle.class);
+
+ doReturn(dataBroker).when(session).getSALService(DataBroker.class);
+ doReturn(mountPointService).when(session).getSALService(MountPointService.class);
+ BDDMockito.given(FrameworkUtil.getBundle(any())).willReturn(bundle);
+ when(bundle.getBundleContext()).thenReturn(bundleContext);
+
+ netconfConsoleProvider.onSessionInitiated(session);
+
+ verify(bundleContext, times(1)).registerService(eq(NetconfCommands.class), any(NetconfCommandsImpl.class), eq(null));
+
+ }
+}
--- /dev/null
+module ietf-inet-types {
+
+ namespace "urn:ietf:params:xml:ns:yang:ietf-inet-types";
+ prefix "inet";
+
+ organization
+ "IETF NETMOD (NETCONF Data Modeling Language) Working Group";
+
+ contact
+ "WG Web: <http://tools.ietf.org/wg/netmod/>
+ WG List: <mailto:netmod@ietf.org>
+
+ WG Chair: David Kessens
+ <mailto:david.kessens@nsn.com>
+
+ WG Chair: Juergen Schoenwaelder
+ <mailto:j.schoenwaelder@jacobs-university.de>
+
+ Editor: Juergen Schoenwaelder
+ <mailto:j.schoenwaelder@jacobs-university.de>";
+
+ description
+ "This module contains a collection of generally useful derived
+ YANG data types for Internet addresses and related things.
+
+ Copyright (c) 2013 IETF Trust and the persons identified as
+ authors of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or
+ without modification, is permitted pursuant to, and subject
+ to the license terms contained in, the Simplified BSD License
+ set forth in Section 4.c of the IETF Trust's Legal Provisions
+ Relating to IETF Documents
+ (http://trustee.ietf.org/license-info).
+
+ This version of this YANG module is part of RFC 6991; see
+ the RFC itself for full legal notices.";
+
+ revision 2013-07-15 {
+ description
+ "This revision adds the following new data types:
+ - ip-address-no-zone
+ - ipv4-address-no-zone
+ - ipv6-address-no-zone";
+ reference
+ "RFC 6991: Common YANG Data Types";
+ }
+
+ revision 2010-09-24 {
+ description
+ "Initial revision.";
+ reference
+ "RFC 6021: Common YANG Data Types";
+ }
+
+ /*** collection of types related to protocol fields ***/
+
+ typedef ip-version {
+ type enumeration {
+ enum unknown {
+ value "0";
+ description
+ "An unknown or unspecified version of the Internet
+ protocol.";
+ }
+ enum ipv4 {
+ value "1";
+ description
+ "The IPv4 protocol as defined in RFC 791.";
+ }
+ enum ipv6 {
+ value "2";
+ description
+ "The IPv6 protocol as defined in RFC 2460.";
+ }
+ }
+ description
+ "This value represents the version of the IP protocol.
+
+ In the value set and its semantics, this type is equivalent
+ to the InetVersion textual convention of the SMIv2.";
+ reference
+ "RFC 791: Internet Protocol
+ RFC 2460: Internet Protocol, Version 6 (IPv6) Specification
+ RFC 4001: Textual Conventions for Internet Network Addresses";
+ }
+
+ typedef dscp {
+ type uint8 {
+ range "0..63";
+ }
+ description
+ "The dscp type represents a Differentiated Services Code Point
+ that may be used for marking packets in a traffic stream.
+ In the value set and its semantics, this type is equivalent
+ to the Dscp textual convention of the SMIv2.";
+ reference
+ "RFC 3289: Management Information Base for the Differentiated
+ Services Architecture
+ RFC 2474: Definition of the Differentiated Services Field
+ (DS Field) in the IPv4 and IPv6 Headers
+ RFC 2780: IANA Allocation Guidelines For Values In
+ the Internet Protocol and Related Headers";
+ }
+
+ typedef ipv6-flow-label {
+ type uint32 {
+ range "0..1048575";
+ }
+ description
+ "The ipv6-flow-label type represents the flow identifier or Flow
+ Label in an IPv6 packet header that may be used to
+ discriminate traffic flows.
+
+ In the value set and its semantics, this type is equivalent
+ to the IPv6FlowLabel textual convention of the SMIv2.";
+ reference
+ "RFC 3595: Textual Conventions for IPv6 Flow Label
+ RFC 2460: Internet Protocol, Version 6 (IPv6) Specification";
+ }
+
+ typedef port-number {
+ type uint16 {
+ range "0..65535";
+ }
+ description
+ "The port-number type represents a 16-bit port number of an
+ Internet transport-layer protocol such as UDP, TCP, DCCP, or
+ SCTP. Port numbers are assigned by IANA. A current list of
+ all assignments is available from <http://www.iana.org/>.
+
+ Note that the port number value zero is reserved by IANA. In
+ situations where the value zero does not make sense, it can
+ be excluded by subtyping the port-number type.
+ In the value set and its semantics, this type is equivalent
+ to the InetPortNumber textual convention of the SMIv2.";
+ reference
+ "RFC 768: User Datagram Protocol
+ RFC 793: Transmission Control Protocol
+ RFC 4960: Stream Control Transmission Protocol
+ RFC 4340: Datagram Congestion Control Protocol (DCCP)
+ RFC 4001: Textual Conventions for Internet Network Addresses";
+ }
+
+ /*** collection of types related to autonomous systems ***/
+
+ typedef as-number {
+ type uint32;
+ description
+ "The as-number type represents autonomous system numbers
+ which identify an Autonomous System (AS). An AS is a set
+ of routers under a single technical administration, using
+ an interior gateway protocol and common metrics to route
+ packets within the AS, and using an exterior gateway
+ protocol to route packets to other ASes. IANA maintains
+ the AS number space and has delegated large parts to the
+ regional registries.
+
+ Autonomous system numbers were originally limited to 16
+ bits. BGP extensions have enlarged the autonomous system
+ number space to 32 bits. This type therefore uses an uint32
+ base type without a range restriction in order to support
+ a larger autonomous system number space.
+
+ In the value set and its semantics, this type is equivalent
+ to the InetAutonomousSystemNumber textual convention of
+ the SMIv2.";
+ reference
+ "RFC 1930: Guidelines for creation, selection, and registration
+ of an Autonomous System (AS)
+ RFC 4271: A Border Gateway Protocol 4 (BGP-4)
+ RFC 4001: Textual Conventions for Internet Network Addresses
+ RFC 6793: BGP Support for Four-Octet Autonomous System (AS)
+ Number Space";
+ }
+
+ /*** collection of types related to IP addresses and hostnames ***/
+
+ typedef ip-address {
+ type union {
+ type inet:ipv4-address;
+ type inet:ipv6-address;
+ }
+ description
+ "The ip-address type represents an IP address and is IP
+ version neutral. The format of the textual representation
+ implies the IP version. This type supports scoped addresses
+ by allowing zone identifiers in the address format.";
+ reference
+ "RFC 4007: IPv6 Scoped Address Architecture";
+ }
+
+ typedef ipv4-address {
+ type string {
+ pattern
+ '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}'
+ + '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'
+ + '(%[\p{N}\p{L}]+)?';
+ }
+ description
+ "The ipv4-address type represents an IPv4 address in
+ dotted-quad notation. The IPv4 address may include a zone
+ index, separated by a % sign.
+
+ The zone index is used to disambiguate identical address
+ values. For link-local addresses, the zone index will
+ typically be the interface index number or the name of an
+ interface. If the zone index is not present, the default
+ zone of the device will be used.
+
+ The canonical format for the zone index is the numerical
+ format";
+ }
+
+ typedef ipv6-address {
+ type string {
+ pattern '((:|[0-9a-fA-F]{0,4}):)([0-9a-fA-F]{0,4}:){0,5}'
+ + '((([0-9a-fA-F]{0,4}:)?(:|[0-9a-fA-F]{0,4}))|'
+ + '(((25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.){3}'
+ + '(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])))'
+ + '(%[\p{N}\p{L}]+)?';
+ pattern '(([^:]+:){6}(([^:]+:[^:]+)|(.*\..*)))|'
+ + '((([^:]+:)*[^:]+)?::(([^:]+:)*[^:]+)?)'
+ + '(%.+)?';
+ }
+ description
+ "The ipv6-address type represents an IPv6 address in full,
+ mixed, shortened, and shortened-mixed notation. The IPv6
+ address may include a zone index, separated by a % sign.
+
+ The zone index is used to disambiguate identical address
+ values. For link-local addresses, the zone index will
+ typically be the interface index number or the name of an
+ interface. If the zone index is not present, the default
+ zone of the device will be used.
+
+ The canonical format of IPv6 addresses uses the textual
+ representation defined in Section 4 of RFC 5952. The
+ canonical format for the zone index is the numerical
+ format as described in Section 11.2 of RFC 4007.";
+ reference
+ "RFC 4291: IP Version 6 Addressing Architecture
+ RFC 4007: IPv6 Scoped Address Architecture
+ RFC 5952: A Recommendation for IPv6 Address Text
+ Representation";
+ }
+
+ typedef ip-address-no-zone {
+ type union {
+ type inet:ipv4-address-no-zone;
+ type inet:ipv6-address-no-zone;
+ }
+ description
+ "The ip-address-no-zone type represents an IP address and is
+ IP version neutral. The format of the textual representation
+ implies the IP version. This type does not support scoped
+ addresses since it does not allow zone identifiers in the
+ address format.";
+ reference
+ "RFC 4007: IPv6 Scoped Address Architecture";
+ }
+
+ typedef ipv4-address-no-zone {
+ type inet:ipv4-address {
+ pattern '[0-9\.]*';
+ }
+ description
+ "An IPv4 address without a zone index. This type, derived from
+ ipv4-address, may be used in situations where the zone is
+ known from the context and hence no zone index is needed.";
+ }
+
+ typedef ipv6-address-no-zone {
+ type inet:ipv6-address {
+ pattern '[0-9a-fA-F:\.]*';
+ }
+ description
+ "An IPv6 address without a zone index. This type, derived from
+ ipv6-address, may be used in situations where the zone is
+ known from the context and hence no zone index is needed.";
+ reference
+ "RFC 4291: IP Version 6 Addressing Architecture
+ RFC 4007: IPv6 Scoped Address Architecture
+ RFC 5952: A Recommendation for IPv6 Address Text
+ Representation";
+ }
+
+ typedef ip-prefix {
+ type union {
+ type inet:ipv4-prefix;
+ type inet:ipv6-prefix;
+ }
+ description
+ "The ip-prefix type represents an IP prefix and is IP
+ version neutral. The format of the textual representations
+ implies the IP version.";
+ }
+
+ typedef ipv4-prefix {
+ type string {
+ pattern
+ '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}'
+ + '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'
+ + '/(([0-9])|([1-2][0-9])|(3[0-2]))';
+ }
+ description
+ "The ipv4-prefix type represents an IPv4 address prefix.
+ The prefix length is given by the number following the
+ slash character and must be less than or equal to 32.
+
+ A prefix length value of n corresponds to an IP address
+ mask that has n contiguous 1-bits from the most
+ significant bit (MSB) and all other bits set to 0.
+
+ The canonical format of an IPv4 prefix has all bits of
+ the IPv4 address set to zero that are not part of the
+ IPv4 prefix.";
+ }
+
+ typedef ipv6-prefix {
+ type string {
+ pattern '((:|[0-9a-fA-F]{0,4}):)([0-9a-fA-F]{0,4}:){0,5}'
+ + '((([0-9a-fA-F]{0,4}:)?(:|[0-9a-fA-F]{0,4}))|'
+ + '(((25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.){3}'
+ + '(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])))'
+ + '(/(([0-9])|([0-9]{2})|(1[0-1][0-9])|(12[0-8])))';
+ pattern '(([^:]+:){6}(([^:]+:[^:]+)|(.*\..*)))|'
+ + '((([^:]+:)*[^:]+)?::(([^:]+:)*[^:]+)?)'
+ + '(/.+)';
+ }
+ description
+ "The ipv6-prefix type represents an IPv6 address prefix.
+ The prefix length is given by the number following the
+ slash character and must be less than or equal to 128.
+
+ A prefix length value of n corresponds to an IP address
+ mask that has n contiguous 1-bits from the most
+ significant bit (MSB) and all other bits set to 0.
+
+ The IPv6 address should have all bits that do not belong
+ to the prefix set to zero.
+
+ The canonical format of an IPv6 prefix has all bits of
+ the IPv6 address set to zero that are not part of the
+ IPv6 prefix. Furthermore, the IPv6 address is represented
+ as defined in Section 4 of RFC 5952.";
+ reference
+ "RFC 5952: A Recommendation for IPv6 Address Text
+ Representation";
+ }
+
+ /*** collection of domain name and URI types ***/
+
+ typedef domain-name {
+ type string {
+ pattern
+ '((([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.)*'
+ + '([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.?)'
+ + '|\.';
+ length "1..253";
+ }
+ description
+ "The domain-name type represents a DNS domain name. The
+ name SHOULD be fully qualified whenever possible.
+
+ Internet domain names are only loosely specified. Section
+ 3.5 of RFC 1034 recommends a syntax (modified in Section
+ 2.1 of RFC 1123). The pattern above is intended to allow
+ for current practice in domain name use, and some possible
+ future expansion. It is designed to hold various types of
+ domain names, including names used for A or AAAA records
+ (host names) and other records, such as SRV records. Note
+ that Internet host names have a stricter syntax (described
+ in RFC 952) than the DNS recommendations in RFCs 1034 and
+ 1123, and that systems that want to store host names in
+ schema nodes using the domain-name type are recommended to
+ adhere to this stricter standard to ensure interoperability.
+
+ The encoding of DNS names in the DNS protocol is limited
+ to 255 characters. Since the encoding consists of labels
+ prefixed by a length bytes and there is a trailing NULL
+ byte, only 253 characters can appear in the textual dotted
+ notation.
+
+ The description clause of schema nodes using the domain-name
+ type MUST describe when and how these names are resolved to
+ IP addresses. Note that the resolution of a domain-name value
+ may require to query multiple DNS records (e.g., A for IPv4
+ and AAAA for IPv6). The order of the resolution process and
+ which DNS record takes precedence can either be defined
+ explicitly or may depend on the configuration of the
+ resolver.
+
+ Domain-name values use the US-ASCII encoding. Their canonical
+ format uses lowercase US-ASCII characters. Internationalized
+ domain names MUST be A-labels as per RFC 5890.";
+ reference
+ "RFC 952: DoD Internet Host Table Specification
+ RFC 1034: Domain Names - Concepts and Facilities
+ RFC 1123: Requirements for Internet Hosts -- Application
+ and Support
+ RFC 2782: A DNS RR for specifying the location of services
+ (DNS SRV)
+ RFC 5890: Internationalized Domain Names in Applications
+ (IDNA): Definitions and Document Framework";
+ }
+
+ typedef host {
+ type union {
+ type inet:ip-address;
+ type inet:domain-name;
+ }
+ description
+ "The host type represents either an IP address or a DNS
+ domain name.";
+ }
+
+ typedef uri {
+ type string;
+ description
+ "The uri type represents a Uniform Resource Identifier
+ (URI) as defined by STD 66.
+
+ Objects using the uri type MUST be in US-ASCII encoding,
+ and MUST be normalized as described by RFC 3986 Sections
+ 6.2.1, 6.2.2.1, and 6.2.2.2. All unnecessary
+ percent-encoding is removed, and all case-insensitive
+ characters are set to lowercase except for hexadecimal
+ digits, which are normalized to uppercase as described in
+ Section 6.2.2.1.
+
+ The purpose of this normalization is to help provide
+ unique URIs. Note that this normalization is not
+ sufficient to provide uniqueness. Two URIs that are
+ textually distinct after this normalization may still be
+ equivalent.
+
+ Objects using the uri type may restrict the schemes that
+ they permit. For example, 'data:' and 'urn:' schemes
+ might not be appropriate.
+
+ A zero-length URI is not a valid URI. This can be used to
+ express 'URI absent' where required.
+
+ In the value set and its semantics, this type is equivalent
+ to the Uri SMIv2 textual convention defined in RFC 5017.";
+ reference
+ "RFC 3986: Uniform Resource Identifier (URI): Generic Syntax
+ RFC 3305: Report from the Joint W3C/IETF URI Planning Interest
+ Group: Uniform Resource Identifiers (URIs), URLs,
+ and Uniform Resource Names (URNs): Clarifications
+ and Recommendations
+ RFC 5017: MIB Textual Conventions for Uniform Resource
+ Identifiers (URIs)";
+ }
+
+}
--- /dev/null
+module netconf-node-topology {
+ namespace "urn:opendaylight:netconf-node-topology";
+ prefix "nettop";
+
+ import network-topology { prefix nt; revision-date 2013-10-21; }
+ import yang-ext { prefix ext; revision-date "2013-07-09";}
+ import ietf-inet-types { prefix inet; revision-date "2013-07-15"; }
+
+ revision "2015-01-14" {
+ description "Initial revision of Topology model";
+ }
+
+ augment "/nt:network-topology/nt:topology/nt:topology-types" {
+ container topology-netconf {
+ }
+ }
+
+ grouping netconf-node-credentials {
+
+ choice credentials {
+ config true;
+ case login-password {
+ leaf username {
+ type string;
+ }
+
+ leaf password {
+ type string;
+ }
+ }
+ }
+ }
+
+ grouping netconf-node-connection-parameters {
+
+ leaf host {
+ type inet:host;
+ }
+
+ leaf port {
+ type inet:port-number;
+ }
+
+ leaf tcp-only {
+ config true;
+ type boolean;
+ }
+
+ leaf schemaless {
+ type boolean;
+ default false;
+ }
+
+ container yang-module-capabilities {
+ config true;
+ leaf override {
+ type boolean;
+ default false;
+ description "Whether to override or merge this list of capabilities with capabilities from device";
+ }
+
+ leaf-list capability {
+ type string;
+ description "Set a list of capabilities to override capabilities provided in device's hello message.
+ Can be used for devices that do not report any yang modules in their hello message";
+ }
+ }
+
+ leaf reconnect-on-changed-schema {
+ config true;
+ type boolean;
+ default false;
+ description "If true, the connector would auto disconnect/reconnect when schemas are changed in the remote device.
+ The connector subscribes (right after connect) to base netconf notifications and listens for netconf-capability-change notification";
+ }
+
+ leaf connection-timeout-millis {
+ description "Specifies timeout in milliseconds after which connection must be established.";
+ config true;
+ type uint32;
+ default 20000;
+ }
+
+ leaf default-request-timeout-millis {
+ description "Timeout for blocking operations within transactions.";
+ config true;
+ type uint32;
+ default 60000;
+ }
+
+ leaf max-connection-attempts {
+ description "Maximum number of connection retries. Non positive value or null is interpreted as infinity.";
+ config true;
+ type uint32;
+ default 0; // retry forever
+ }
+
+ leaf between-attempts-timeout-millis {
+ description "Initial timeout in milliseconds to wait between connection attempts. Will be multiplied by sleep-factor with every additional attempt";
+ config true;
+ type uint16;
+ default 2000;
+ }
+
+ leaf sleep-factor {
+ config true;
+ type decimal64 {
+ fraction-digits 1;
+ }
+ default 1.5;
+ }
+
+ // Keepalive configuration
+ leaf keepalive-delay {
+ config true;
+ type uint32;
+ default 120;
+ description "Netconf connector sends keepalive RPCs while the session is idle, this delay specifies the delay between keepalive RPC in seconds
+ If a value <1 is provided, no keepalives will be sent";
+ }
+
+ leaf concurrent-rpc-limit {
+ config true;
+ type uint16;
+ default 0;
+ description "Limit of concurrent messages that can be send before reply messages are received.
+ If value <1 is provided, no limit will be enforced";
+ }
+ }
+
+ grouping netconf-node-connection-status {
+
+ leaf connection-status {
+ config false;
+ type enumeration {
+ enum connecting;
+ enum connected;
+ enum unable-to-connect;
+ }
+ }
+
+ container clustered-connection-status {
+ config false;
+ list node-status {
+ leaf node {
+ type string;
+ }
+ leaf status {
+ type enumeration {
+ enum connected;
+ enum unavailable;
+ enum failed;
+ }
+ }
+ }
+ }
+
+ leaf connected-message {
+ config false;
+ type string;
+ }
+
+ container available-capabilities {
+ config false;
+ leaf-list available-capability {
+ type string;
+ }
+ }
+
+ container unavailable-capabilities {
+ config false;
+ list unavailable-capability {
+ leaf capability {
+ type string;
+ }
+
+ leaf failure-reason {
+ type enumeration {
+ enum missing-source;
+ enum unable-to-resolve;
+ }
+ }
+ }
+ }
+
+ container pass-through {
+ when "../connection-status = connected";
+ description
+ "When the underlying node is connected, its NETCONF context
+ is available verbatim under this container through the
+ mount extension.";
+ }
+
+ }
+
+ grouping netconf-schema-storage {
+ leaf schema-cache-directory {
+ config true;
+ type string;
+ default "schema";
+ description "The destination schema repository for yang files relative to the cache directory. This may be specified per netconf mount
+ so that the loaded yang files are stored to a distinct directory to avoid potential conflict.";
+ }
+
+ container yang-library {
+ leaf yang-library-url {
+ config true;
+ type inet:uri;
+ description "Yang library to be plugged as additional source provider into the shared schema repository";
+ }
+
+ // credentials for basic http authentication
+ leaf username {
+ config true;
+ type string;
+ }
+
+ leaf password {
+ config true;
+ type string;
+ }
+ }
+ }
+
+ grouping netconf-node-fields {
+
+ uses netconf-node-credentials;
+
+ uses netconf-node-connection-parameters;
+
+ uses netconf-node-connection-status;
+
+ uses netconf-schema-storage;
+
+ }
+
+ augment "/nt:network-topology/nt:topology/nt:node" {
+ when "../../nt:topology-types/topology-netconf";
+ ext:augment-identifier "netconf-node";
+
+ uses netconf-node-fields;
+ }
+}
--- /dev/null
+module network-topology {
+ yang-version 1;
+ namespace "urn:TBD:params:xml:ns:yang:network-topology";
+ // replace with IANA namespace when assigned
+ prefix "nt";
+
+ import ietf-inet-types { prefix "inet"; revision-date 2013-07-15; }
+
+ organization "TBD";
+
+ contact "WILL-BE-DEFINED-LATER";
+
+ description
+ "This module defines a model for the topology of a network.
+ Key design decisions are as follows:
+ A topology consists of a set of nodes and links.
+ Links are point-to-point and unidirectional.
+ Bidirectional connections need to be represented through
+ two separate links.
+ Multipoint connections, broadcast domains etc can be represented
+ through a hierarchy of nodes, then connecting nodes at
+ upper layers of the hierarchy.";
+
+ revision 2013-10-21 {
+ description
+ "Initial revision.";
+ }
+
+ typedef topology-id {
+ type inet:uri;
+ description
+ "An identifier for a topology.";
+ }
+
+ typedef node-id {
+ type inet:uri;
+ description
+ "An identifier for a node in a topology.
+ The identifier may be opaque.
+ The identifier SHOULD be chosen such that the same node in a
+ real network topology will always be identified through the
+ same identifier, even if the model is instantiated in separate
+ datastores. An implementation MAY choose to capture semantics
+ in the identifier, for example to indicate the type of node
+ and/or the type of topology that the node is a part of.";
+ }
+
+
+ typedef link-id {
+ type inet:uri;
+ description
+ "An identifier for a link in a topology.
+ The identifier may be opaque.
+ The identifier SHOULD be chosen such that the same link in a
+ real network topology will always be identified through the
+ same identifier, even if the model is instantiated in separate
+ datastores. An implementation MAY choose to capture semantics
+ in the identifier, for example to indicate the type of link
+ and/or the type of topology that the link is a part of.";
+ }
+
+ typedef tp-id {
+ type inet:uri;
+ description
+ "An identifier for termination points on a node.
+ The identifier may be opaque.
+ The identifier SHOULD be chosen such that the same TP in a
+ real network topology will always be identified through the
+ same identifier, even if the model is instantiated in separate
+ datastores. An implementation MAY choose to capture semantics
+ in the identifier, for example to indicate the type of TP
+ and/or the type of node and topology that the TP is a part of.";
+ }
+
+ typedef tp-ref {
+ type leafref {
+ path "/network-topology/topology/node/termination-point/tp-id";
+ }
+ description
+ "A type for an absolute reference to a termination point.
+ (This type should not be used for relative references.
+ In such a case, a relative path should be used instead.)";
+ }
+ typedef topology-ref {
+ type leafref {
+ path "/network-topology/topology/topology-id";
+ }
+ description
+ "A type for an absolute reference a topology instance.";
+ }
+
+ typedef node-ref {
+ type leafref {
+ path "/network-topology/topology/node/node-id";
+ }
+ description
+
+ "A type for an absolute reference to a node instance.
+ (This type should not be used for relative references.
+ In such a case, a relative path should be used instead.)";
+ }
+
+ typedef link-ref {
+ type leafref {
+ path "/network-topology/topology/link/link-id";
+ }
+ description
+ "A type for an absolute reference a link instance.
+ (This type should not be used for relative references.
+ In such a case, a relative path should be used instead.)";
+ }
+
+ grouping tp-attributes {
+ description
+ "The data objects needed to define a termination point.
+ (This only includes a single leaf at this point, used
+ to identify the termination point.)
+ Provided in a grouping so that in addition to the datastore,
+ the data can also be included in notifications.";
+ leaf tp-id {
+ type tp-id;
+ }
+ leaf-list tp-ref {
+ type tp-ref;
+ config false;
+ description
+ "The leaf list identifies any termination points that the
+ termination point is dependent on, or maps onto.
+ Those termination points will themselves be contained
+ in a supporting node.
+ This dependency information can be inferred from
+ the dependencies between links. For this reason,
+ this item is not separately configurable. Hence no
+ corresponding constraint needs to be articulated.
+ The corresponding information is simply provided by the
+ implementing system.";
+ }
+ }
+
+ grouping node-attributes {
+ description
+ "The data objects needed to define a node.
+ The objects are provided in a grouping so that in addition to
+ the datastore, the data can also be included in notifications
+ as needed.";
+
+ leaf node-id {
+ type node-id;
+ description
+ "The identifier of a node in the topology.
+ A node is specific to a topology to which it belongs.";
+ }
+ list supporting-node {
+ description
+ "This list defines vertical layering information for nodes.
+ It allows to capture for any given node, which node (or nodes)
+ in the corresponding underlay topology it maps onto.
+ A node can map to zero, one, or more nodes below it;
+ accordingly there can be zero, one, or more elements in the list.
+ If there are specific layering requirements, for example
+ specific to a particular type of topology that only allows
+ for certain layering relationships, the choice
+ below can be augmented with additional cases.
+ A list has been chosen rather than a leaf-list in order
+ to provide room for augmentations, e.g. for
+ statistics or priorization information associated with
+ supporting nodes.";
+ // This is not what was published in the initial draft,
+ // added topology-ref leaf and added it to the key
+ key "topology-ref node-ref";
+ leaf topology-ref {
+ type topology-ref;
+ }
+ leaf node-ref {
+ type node-ref;
+ }
+ }
+ }
+
+ grouping link-attributes {
+ // This is a grouping, not defined inline with the link definition itself,
+ // so it can be included in a notification, if needed
+ leaf link-id {
+ type link-id;
+ description
+ "The identifier of a link in the topology.
+ A link is specific to a topology to which it belongs.";
+ }
+ container source {
+ leaf source-node {
+ mandatory true;
+ type node-ref;
+ description
+ "Source node identifier, must be in same topology.";
+ }
+ leaf source-tp {
+ type tp-ref;
+ description
+ "Termination point within source node that terminates the link.";
+
+ }
+ }
+ container destination {
+ leaf dest-node {
+ mandatory true;
+ type node-ref;
+ description
+ "Destination node identifier, must be in same topology.";
+ }
+ leaf dest-tp {
+ type tp-ref;
+ description
+ "Termination point within destination node that terminates the link.";
+ }
+ }
+ list supporting-link {
+ key "link-ref";
+ leaf link-ref {
+ type link-ref;
+ }
+ }
+ }
+
+
+ container network-topology {
+ list topology {
+ description "
+ This is the model of an abstract topology.
+ A topology contains nodes and links.
+ Each topology MUST be identified by
+ unique topology-id for reason that a network could contain many
+ topologies.
+ ";
+ key "topology-id";
+ leaf topology-id {
+ type topology-id;
+ description "
+ It is presumed that a datastore will contain many topologies. To
+ distinguish between topologies it is vital to have UNIQUE
+ topology identifiers.
+ ";
+ }
+ leaf server-provided {
+ type boolean;
+ config false;
+ description "
+ Indicates whether the topology is configurable by clients,
+ or whether it is provided by the server. This leaf is
+
+ populated by the server implementing the model.
+ It is set to false for topologies that are created by a client;
+ it is set to true otherwise. If it is set to true, any
+ attempt to edit the topology MUST be rejected.
+ ";
+ }
+ container topology-types {
+ description
+ "This container is used to identify the type, or types
+ (as a topology can support several types simultaneously),
+ of the topology.
+ Topology types are the subject of several integrity constraints
+ that an implementing server can validate in order to
+ maintain integrity of the datastore.
+ Topology types are indicated through separate data nodes;
+ the set of topology types is expected to increase over time.
+ To add support for a new topology, an augmenting module
+ needs to augment this container with a new empty optional
+ container to indicate the new topology type.
+ The use of a container allows to indicate a subcategorization
+ of topology types.
+ The container SHALL NOT be augmented with any data nodes
+ that serve a purpose other than identifying a particular
+ topology type.
+ ";
+ }
+ list underlay-topology {
+ key "topology-ref";
+ leaf topology-ref {
+ type topology-ref;
+ }
+ // a list, not a leaf-list, to allow for potential augmentation
+ // with properties specific to the underlay topology,
+ // such as statistics, preferences, or cost.
+ description
+ "Identifies the topology, or topologies, that this topology
+ is dependent on.";
+ }
+
+ list node {
+ description "The list of network nodes defined for the topology.";
+ key "node-id";
+ uses node-attributes;
+ must "boolean(../underlay-topology[*]/node[./supporting-nodes/node-ref])";
+ // This constraint is meant to ensure that a referenced node is in fact
+ // a node in an underlay topology.
+ list termination-point {
+ description
+
+ "A termination point can terminate a link.
+ Depending on the type of topology, a termination point could,
+ for example, refer to a port or an interface.";
+ key "tp-id";
+ uses tp-attributes;
+ }
+ }
+
+ list link {
+ description "
+ A Network Link connects a by Local (Source) node and
+ a Remote (Destination) Network Nodes via a set of the
+ nodes' termination points.
+ As it is possible to have several links between the same
+ source and destination nodes, and as a link could potentially
+ be re-homed between termination points, to ensure that we
+ would always know to distinguish between links, every link
+ is identified by a dedicated link identifier.
+ Note that a link models a point-to-point link, not a multipoint
+ link.
+ Layering dependencies on links in underlay topologies are
+ not represented as the layering information of nodes and of
+ termination points is sufficient.
+ ";
+ key "link-id";
+ uses link-attributes;
+ must "boolean(../underlay-topology/link[./supporting-link])";
+ // Constraint: any supporting link must be part of an underlay topology
+ must "boolean(../node[./source/source-node])";
+ // Constraint: A link must have as source a node of the same topology
+ must "boolean(../node[./destination/dest-node])";
+ // Constraint: A link must have as source a destination of the same topology
+ must "boolean(../node/termination-point[./source/source-tp])";
+ // Constraint: The source termination point must be contained in the source node
+ must "boolean(../node/termination-point[./destination/dest-tp])";
+ // Constraint: The destination termination point must be contained
+ // in the destination node
+ }
+ }
+ }
+}
--- /dev/null
+module yang-ext {
+ yang-version 1;
+ namespace "urn:opendaylight:yang:extension:yang-ext";
+ prefix "ext";
+
+ contact "Anton Tkacik <ttkacik@cisco.com>";
+
+ description
+ "Copyright (c) 2013 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";
+
+ revision "2013-07-09" {
+ description "";
+ }
+
+ // Augmentation name
+
+ extension "augment-identifier" {
+ description
+ "YANG language extension which assigns an identifier to
+ augmentation. Augment identifier is used to identify
+ specific augment statement by name.
+
+ The identifier syntax is defined formally defined by the rule
+ 'identifier' in Section 12 of RFC 6020.
+
+ All augment identifiers defined in a namespace MUST be unique.
+ The namespace of augment identifiers is shared by module and
+ its submodules.";
+
+ /*
+ Discussion:
+ This extension allows for ease of development / debug
+ of YANG modules and it is suitable for code generation,
+ where each augment statement is nicely identified by
+ unique name instead of combination of augment target
+ and when condition.
+ */
+ argument "identifier";
+ }
+
+
+ // Context-aware RPCs
+
+ grouping rpc-context-ref {
+ description
+ "A reference to RPC context.";
+ leaf context-instance {
+ type instance-identifier;
+ description "Pointer to the context. ";
+ }
+ }
+
+ extension "rpc-context-instance" {
+ description
+ "YANG language extension which defines enclosing (parent)
+ schema node as referencable context for RPCs.
+
+ The argument is identity which is used to identify RPC context
+ type.";
+
+ argument "context-type";
+ }
+
+ extension "context-reference" {
+ argument "context-type";
+ }
+
+ extension "context-instance" {
+ argument "context-type";
+ }
+
+ extension "instance-target" {
+ argument "path";
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 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.netconf.notifications;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import javax.xml.datatype.DatatypeConfigurationException;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+public class NetconfNotificationTest {
+
+ @Test
+ public void testWrapNotification() throws Exception {
+ final DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
+ final DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
+
+ final Document document = docBuilder.newDocument();
+
+ final Element rootElement = document.createElement("test-root");
+ document.appendChild(rootElement);
+
+ final Date eventTime = new Date();
+ eventTime.setTime(10000000);
+
+ final NetconfNotification netconfNotification = new NetconfNotification(document, eventTime);
+ final Document resultDoc = netconfNotification.getDocument();
+ final NodeList nodeList = resultDoc.getElementsByTagNameNS(NetconfNotification.NOTIFICATION_NAMESPACE,
+ NetconfNotification.NOTIFICATION);
+
+ assertNotNull(nodeList);
+ // expected only the one NOTIFICATION tag
+ assertEquals(1, nodeList.getLength());
+
+ final Element entireNotification = (Element) nodeList.item(0);
+ final NodeList childNodes = entireNotification.getElementsByTagNameNS(
+ NetconfNotification.NOTIFICATION_NAMESPACE, NetconfNotification.EVENT_TIME);
+
+ assertNotNull(childNodes);
+ // expected only the one EVENT_TIME tag
+ assertEquals(1, childNodes.getLength());
+
+ final Element eventTimeElement = (Element) childNodes.item(0);
+
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+ assertEquals(eventTime.getTime(), sdf.parse(eventTimeElement.getTextContent()).getTime());
+
+ assertEquals(eventTime, netconfNotification.getEventTime());
+
+ }
+}