<artifactId>xmlunit</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ </dependency>
</dependencies>
<build>
import org.opendaylight.netconf.notifications.BaseNotificationPublisherRegistration;
import org.opendaylight.netconf.notifications.NetconfNotificationCollector;
import org.opendaylight.netconf.util.osgi.NetconfConfigUtil;
+import org.opendaylight.netconf.util.osgi.NetconfConfiguration;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
SessionIdProvider idProvider = new SessionIdProvider();
timer = new HashedWheelTimer();
- long connectionTimeoutMillis = NetconfConfigUtil.extractTimeoutMillis(context);
+
+ long connectionTimeoutMillis = NetconfConfiguration.DEFAULT_TIMEOUT_MILLIS;
final NetconfMonitoringServiceImpl monitoringService = startMonitoringService(context, factoriesListener);
serverNegotiatorFactory);
NetconfServerDispatcherImpl dispatch = new NetconfServerDispatcherImpl(serverChannelInitializer, eventLoopGroup, eventLoopGroup);
- LocalAddress address = NetconfConfigUtil.getNetconfLocalAddress();
+ LocalAddress address = NetconfConfiguration.NETCONF_LOCAL_ADDRESS;
LOG.trace("Starting local netconf server at {}", address);
dispatch.createLocalServer(address);
final InetSocketAddress sshSocketAddress = netconfConfiguration.getSshServerAddress();
LOG.info("Starting netconf SSH server at {}", sshSocketAddress);
- final LocalAddress localAddress = NetconfConfigUtil.getNetconfLocalAddress();
+ final LocalAddress localAddress = NetconfConfiguration.NETCONF_LOCAL_ADDRESS;
authProviderTracker = new AuthProviderTracker(bundleContext);
final String path = netconfConfiguration.getPrivateKeyPath();
import io.netty.handler.logging.LoggingHandler;
import java.io.BufferedReader;
import java.io.InputStreamReader;
-import org.opendaylight.netconf.util.osgi.NetconfConfigUtil;
+import org.opendaylight.netconf.util.osgi.NetconfConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
});
// Start the server.
- LocalAddress localAddress = NetconfConfigUtil.getNetconfLocalAddress();
+ LocalAddress localAddress = NetconfConfiguration.NETCONF_LOCAL_ADDRESS;
ChannelFuture f = b.bind(localAddress).sync();
// Wait until the server socket is closed.
import io.netty.handler.logging.LoggingHandler;
import java.net.InetSocketAddress;
import org.opendaylight.netconf.util.osgi.NetconfConfigUtil;
+import org.opendaylight.netconf.util.osgi.NetconfConfiguration;
public class ProxyServer implements Runnable {
private final ProxyHandlerFactory proxyHandlerFactory;
final EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
- final LocalAddress localAddress = NetconfConfigUtil.getNetconfLocalAddress();
+ final LocalAddress localAddress = NetconfConfiguration.NETCONF_LOCAL_ADDRESS;
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
import org.opendaylight.netconf.nettyutil.handler.ssh.client.AsyncSshHandler;
import org.opendaylight.netconf.ssh.SshProxyServer;
import org.opendaylight.netconf.ssh.SshProxyServerConfigurationBuilder;
-import org.opendaylight.netconf.util.osgi.NetconfConfigUtil;
+import org.opendaylight.netconf.util.osgi.NetconfConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
final InetSocketAddress addr = new InetSocketAddress("127.0.0.1", 10831);
final SshProxyServer sshProxyServer = new SshProxyServer(minaTimerEx, nettyGroup, nioExec);
sshProxyServer.bind(
- new SshProxyServerConfigurationBuilder().setBindingAddress(addr).setLocalAddress(NetconfConfigUtil.getNetconfLocalAddress()).setAuthenticator(new AuthProvider() {
+ new SshProxyServerConfigurationBuilder().setBindingAddress(addr).setLocalAddress(NetconfConfiguration.NETCONF_LOCAL_ADDRESS).setAuthenticator(new AuthProvider() {
@Override
public boolean authenticated(final String username, final String password) {
return true;
import org.opendaylight.netconf.ssh.SshProxyServer;
import org.opendaylight.netconf.ssh.SshProxyServerConfigurationBuilder;
import org.opendaylight.netconf.util.osgi.NetconfConfigUtil;
+import org.opendaylight.netconf.util.osgi.NetconfConfiguration;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
final InetSocketAddress addr = InetSocketAddress.createUnresolved(HOST, PORT);
server = new SshProxyServer(minaTimerEx, clientGroup, nioExec);
server.bind(
- new SshProxyServerConfigurationBuilder().setBindingAddress(addr).setLocalAddress(NetconfConfigUtil.getNetconfLocalAddress()).setAuthenticator(new AuthProvider() {
+ new SshProxyServerConfigurationBuilder().setBindingAddress(addr).setLocalAddress(NetconfConfiguration.NETCONF_LOCAL_ADDRESS).setAuthenticator(new AuthProvider() {
@Override
public boolean authenticated(final String username, final String password) {
return true;
import java.net.InetSocketAddress;
import org.opendaylight.netconf.tcp.netty.ProxyServer;
import org.opendaylight.netconf.util.osgi.NetconfConfigUtil;
-import org.opendaylight.netconf.util.osgi.NetconfConfigUtil.InfixProp;
import org.opendaylight.netconf.util.osgi.NetconfConfiguration;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
final InetSocketAddress address = netconfConfiguration.getTcpServerAddress();
if (address.getAddress().isAnyLocalAddress()) {
- LOG.warn("Unprotected netconf TCP address is configured to ANY local address. This is a security risk. Consider changing {} to 127.0.0.1",
- NetconfConfigUtil.getNetconfServerAddressKey(InfixProp.tcp));
+ LOG.warn("Unprotected netconf TCP address is configured to ANY local address. This is a security risk. " +
+ "Consider changing tcp-address in netconf.cfg to 127.0.0.1");
}
LOG.info("Starting TCP netconf server at {}", address);
- proxyServer = new ProxyServer(address, NetconfConfigUtil.getNetconfLocalAddress());
+ proxyServer = new ProxyServer(address, NetconfConfiguration.NETCONF_LOCAL_ADDRESS);
}
@Override
package org.opendaylight.netconf.util.osgi;
-import com.google.common.base.Optional;
-import io.netty.channel.local.LocalAddress;
-import java.net.InetSocketAddress;
import java.util.Collection;
-import java.util.concurrent.TimeUnit;
+import java.util.Optional;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
public final class NetconfConfigUtil {
private static final Logger LOG = LoggerFactory.getLogger(NetconfConfigUtil.class);
- private static final String PREFIX_PROP = "netconf.";
-
private NetconfConfigUtil() {
}
- public enum InfixProp {
- tcp, ssh
- }
-
- private static final String PORT_SUFFIX_PROP = ".port";
- private static final String ADDRESS_SUFFIX_PROP = ".address";
- private static final String PRIVATE_KEY_PATH_PROP = ".pk.path";
-
- private static final String CONNECTION_TIMEOUT_MILLIS_PROP = "connectionTimeoutMillis";
- private static final String LOCAL_HOST = "127.0.0.1";
- private static final String INADDR_ANY = "0.0.0.0";
- public static final long DEFAULT_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(30);
- private static final LocalAddress NETCONF_LOCAL_ADDRESS = new LocalAddress("netconf");
- public static final String DEFAULT_PRIVATE_KEY_PATH = "./configuration/RSA.pk";
- public static final InetSocketAddress DEFAULT_TCP_SERVER_ADRESS = new InetSocketAddress(LOCAL_HOST, 8383);
- public static final InetSocketAddress DEFAULT_SSH_SERVER_ADRESS = new InetSocketAddress(INADDR_ANY, 1830);
-
- public static LocalAddress getNetconfLocalAddress() {
- return NETCONF_LOCAL_ADDRESS;
- }
-
- public static long extractTimeoutMillis(final BundleContext bundleContext) {
- final String key = PREFIX_PROP + CONNECTION_TIMEOUT_MILLIS_PROP;
- final String timeoutString = bundleContext.getProperty(key);
- if (timeoutString == null || timeoutString.length() == 0) {
- return DEFAULT_TIMEOUT_MILLIS;
- }
- try {
- return Long.parseLong(timeoutString);
- } catch (final NumberFormatException e) {
- LOG.warn("Cannot parse {} property: {}, using defaults", key, timeoutString, e);
- return DEFAULT_TIMEOUT_MILLIS;
- }
- }
-
- /**
- * @param context from which properties are being read.
- * @return value of private key path if value is present, Optional.absent otherwise
- */
- public static Optional<String> getPrivateKeyPath(final BundleContext context) {
- return getProperty(context, getPrivateKeyKey());
- }
-
- public static String getPrivateKeyKey() {
- return PREFIX_PROP + InfixProp.ssh + PRIVATE_KEY_PATH_PROP;
- }
-
- public static String getNetconfServerAddressKey(final InfixProp infixProp) {
- return PREFIX_PROP + infixProp + ADDRESS_SUFFIX_PROP;
- }
-
- /**
- * @param context from which properties are being read.
- * @param infixProp either tcp or ssh
- * @return value if address and port are present and valid, Optional.absent otherwise.
- */
- public static Optional<InetSocketAddress> extractNetconfServerAddress(final BundleContext context,
- final InfixProp infixProp) {
-
- final Optional<String> address = getProperty(context, getNetconfServerAddressKey(infixProp));
- final Optional<String> port = getProperty(context, PREFIX_PROP + infixProp + PORT_SUFFIX_PROP);
-
- if (address.isPresent() && port.isPresent()) {
- try {
- return Optional.of(parseAddress(address, port));
- } catch (final IllegalArgumentException | SecurityException e) {
- LOG.warn("Unable to parse {} netconf address from {}:{}, fallback to default",
- infixProp, address, port, e);
- }
- }
- return Optional.absent();
- }
-
- private static InetSocketAddress parseAddress(final Optional<String> address, final Optional<String> port) {
- final int portNumber = Integer.valueOf(port.get());
- return new InetSocketAddress(address.get(), portNumber);
- }
-
- private static Optional<String> getProperty(final BundleContext context, final String propKey) {
- String value = context.getProperty(propKey);
- if (value != null && value.isEmpty()) {
- value = null;
- }
- return Optional.fromNullable(value);
- }
-
- public static java.util.Optional<NetconfConfiguration> getNetconfConfigurationService(BundleContext bundleContext) {
+ public static Optional<NetconfConfiguration> getNetconfConfigurationService(BundleContext bundleContext) {
final Collection<ServiceReference<ManagedService>> serviceReferences;
try {
serviceReferences = bundleContext.getServiceReferences(ManagedService.class, null);
for (final ServiceReference<ManagedService> serviceReference : serviceReferences) {
ManagedService service = bundleContext.getService(serviceReference);
if (service instanceof NetconfConfiguration){
- return java.util.Optional.of((NetconfConfiguration) service);
+ return Optional.of((NetconfConfiguration) service);
}
}
} catch (InvalidSyntaxException e) {
LOG.error("Unable to retrieve references for ManagedService: {}", e);
}
- LOG.error("Unable to retrieve NetconfConfiguration service. Not found. Bundle netconf-util probably failed.");
- return java.util.Optional.empty();
+ LOG.error("Unable to retrieve NetconfConfiguration service. Not found. Bundle netconf-util probably failed to load.");
+ return Optional.empty();
}
}
package org.opendaylight.netconf.util.osgi;
+import io.netty.channel.local.LocalAddress;
import java.net.InetSocketAddress;
import java.util.Dictionary;
+import java.util.concurrent.TimeUnit;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class NetconfConfiguration implements ManagedService {
private static final Logger LOG = LoggerFactory.getLogger(NetconfConfiguration.class);
- private static final NetconfConfiguration instance = new NetconfConfiguration();
- private NetconfConfigurationHolder netconfConfiguration;
+ /**
+ * Props to access information within the dictionary.
+ */
+
+ private static final String SSH_ADDRESS_PROP = "ssh-address";
+ private static final String SSH_PORT_PROP = "ssh-port";
+ private static final String TCP_ADDRESS_PROP = "tcp-address";
+ private static final String TCP_PORT_PROP = "tcp-port";
+ private static final String SSH_PK_PATH_PROP = "ssh-pk-path";
+
+ /**
+ * Default values used if no dictionary is provided.
+ */
+
+ public static final LocalAddress NETCONF_LOCAL_ADDRESS = new LocalAddress("netconf");
+ public static final long DEFAULT_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(30);
- public static final String KEY_SSH_ADDRESS = "ssh-address";
- public static final String KEY_SSH_PORT = "ssh-port";
- public static final String KEY_TCP_ADDRESS = "tcp-address";
- public static final String KEY_TCP_PORT = "tcp-port";
- public static final String KEY_SSH_PK_PATH = "ssh-pk-path";
+ private static final String LOCAL_HOST = "127.0.0.1";
+ private static final String INADDR_ANY = "0.0.0.0";
+ private static final String DEFAULT_PRIVATE_KEY_PATH = "./configuration/RSA.pk";
+ private static final InetSocketAddress DEFAULT_TCP_SERVER_ADRESS = new InetSocketAddress(LOCAL_HOST, 8383);
+ private static final InetSocketAddress DEFAULT_SSH_SERVER_ADRESS = new InetSocketAddress(INADDR_ANY, 1830);
+
+ /**
+ * Singleton instance of this class, registered as a service in the OSGi registry.
+ */
+ private static final NetconfConfiguration INSTANCE = new NetconfConfiguration();
+
+ private NetconfConfigurationHolder netconfConfiguration;
public static NetconfConfiguration getInstance() {
- return instance;
+ return INSTANCE;
}
private NetconfConfiguration() {
- netconfConfiguration = new NetconfConfigurationHolder(NetconfConfigUtil.DEFAULT_TCP_SERVER_ADRESS,
- NetconfConfigUtil.DEFAULT_SSH_SERVER_ADRESS, NetconfConfigUtil.DEFAULT_PRIVATE_KEY_PATH);
+ netconfConfiguration = new NetconfConfigurationHolder(DEFAULT_TCP_SERVER_ADRESS,
+ DEFAULT_SSH_SERVER_ADRESS, DEFAULT_PRIVATE_KEY_PATH);
}
@Override
public void updated(final Dictionary<String, ?> dictionaryConfig) {
if (dictionaryConfig == null) {
- LOG.warn("Netconf configuration cannot be updated.");
+ LOG.trace("CSS netconf server configuration cannot be updated as passed dictionary is null");
return;
}
- final InetSocketAddress sshServerAddress = new InetSocketAddress((String) dictionaryConfig.get(KEY_SSH_ADDRESS),
- Integer.parseInt((String) dictionaryConfig.get(KEY_SSH_PORT)));
- final InetSocketAddress tcpServerAddress = new InetSocketAddress((String) dictionaryConfig.get(KEY_TCP_ADDRESS),
- Integer.parseInt((String) dictionaryConfig.get(KEY_TCP_PORT)));
+ final InetSocketAddress sshServerAddress = new InetSocketAddress((String) dictionaryConfig.get(SSH_ADDRESS_PROP),
+ Integer.parseInt((String) dictionaryConfig.get(SSH_PORT_PROP)));
+ final InetSocketAddress tcpServerAddress = new InetSocketAddress((String) dictionaryConfig.get(TCP_ADDRESS_PROP),
+ Integer.parseInt((String) dictionaryConfig.get(TCP_PORT_PROP)));
- netconfConfiguration = new NetconfConfigurationHolder(tcpServerAddress, sshServerAddress,
- (String) dictionaryConfig.get(KEY_SSH_PK_PATH));
+ netconfConfiguration = new NetconfConfigurationHolder(tcpServerAddress,
+ sshServerAddress,
+ (String) dictionaryConfig.get(SSH_PK_PATH_PROP));
- LOG.info("Netconf configuration was updated: {}", dictionaryConfig.toString());
+ LOG.debug("CSS netconf server configuration was updated: {}", dictionaryConfig.toString());
}
public InetSocketAddress getSshServerAddress(){
@Override
public void stop(BundleContext bundleContext) {
if (configService != null) {
- configService.unregister();
- configService = null;
+ configService.unregister();
+ configService = null;
}
}
- private Hashtable<String, String> getNetconfConfigProperties(){
+ private Hashtable<String, String> getNetconfConfigProperties() {
Hashtable<String, String> properties = new Hashtable<>();
properties.put(Constants.SERVICE_PID, CONFIG_PID);
return properties;
private final InetSocketAddress sshServerAddress;
private final String privateKeyPath;
- NetconfConfigurationHolder(InetSocketAddress tcpServerAddress, InetSocketAddress sshServerAddress, String privateKeyPath){
+ NetconfConfigurationHolder(final InetSocketAddress tcpServerAddress,
+ final InetSocketAddress sshServerAddress,
+ final String privateKeyPath) {
this.tcpServerAddress = tcpServerAddress;
this.sshServerAddress = sshServerAddress;
this.privateKeyPath = privateKeyPath;
InetSocketAddress getTcpServerAddress() {
return tcpServerAddress;
}
-
}
/*
- * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
+ * 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,
package org.opendaylight.netconf.util.osgi;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
-import com.google.common.base.Optional;
-import io.netty.channel.local.LocalAddress;
-import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collection;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cm.ManagedService;
public class NetconfConfigUtilTest {
- private BundleContext bundleContext;
+ @Mock
+ private ServiceReference<ManagedService> serviceRef;
- @Before
- public void setUp() throws Exception {
- bundleContext = mock(BundleContext.class);
- }
-
- @Test
- public void testNetconfConfigUtil() throws Exception {
- assertEquals(NetconfConfigUtil.getNetconfLocalAddress(), new LocalAddress("netconf"));
-
- doReturn("").when(bundleContext).getProperty("netconf.connectionTimeoutMillis");
- assertEquals(NetconfConfigUtil.extractTimeoutMillis(bundleContext), NetconfConfigUtil.DEFAULT_TIMEOUT_MILLIS);
-
- doReturn("a").when(bundleContext).getProperty("netconf.connectionTimeoutMillis");
- assertEquals(NetconfConfigUtil.extractTimeoutMillis(bundleContext), NetconfConfigUtil.DEFAULT_TIMEOUT_MILLIS);
- }
+ @Mock
+ private ServiceReference<ManagedService> netconfConfigurationRef;
- @Test
- public void testgetPrivateKeyKey() throws Exception {
- assertEquals(NetconfConfigUtil.getPrivateKeyKey(), "netconf.ssh.pk.path");
- }
-
- @Test
- public void testgetNetconfServerAddressKey() throws Exception {
- NetconfConfigUtil.InfixProp prop = NetconfConfigUtil.InfixProp.tcp;
- assertEquals(NetconfConfigUtil.getNetconfServerAddressKey(prop), "netconf.tcp.address");
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
}
@Test
- public void testExtractNetconfServerAddress() throws Exception {
- NetconfConfigUtil.InfixProp prop = NetconfConfigUtil.InfixProp.tcp;
- doReturn("").when(bundleContext).getProperty(anyString());
- assertEquals(NetconfConfigUtil.extractNetconfServerAddress(bundleContext, prop), Optional.absent());
- }
+ public void testGetNetconfConfigurationService() throws Exception {
+ final Collection<ServiceReference<ManagedService>> services = new ArrayList<>();
+ services.add(serviceRef);
+ services.add(netconfConfigurationRef);
+ final BundleContext context = mock(BundleContext.class);
+ doReturn(services).when(context).getServiceReferences(ManagedService.class, null);
+ final ManagedService service = mock(ManagedService.class);
+ doReturn(service).when(context).getService(serviceRef);
+ doReturn(NetconfConfiguration.getInstance()).when(context).getService(netconfConfigurationRef);
+ final java.util.Optional<NetconfConfiguration> netconfConfigurationOptional =
+ NetconfConfigUtil.getNetconfConfigurationService(context);
+ Assert.assertTrue(netconfConfigurationOptional.isPresent());
+ Assert.assertEquals(NetconfConfiguration.getInstance(), netconfConfigurationOptional.get());
- @Test
- public void testExtractNetconfServerAddress2() throws Exception {
- NetconfConfigUtil.InfixProp prop = NetconfConfigUtil.InfixProp.tcp;
- doReturn("1.1.1.1").when(bundleContext).getProperty("netconf.tcp.address");
- doReturn("20").when(bundleContext).getProperty("netconf.tcp.port");
- Optional<InetSocketAddress> inetSocketAddressOptional = NetconfConfigUtil.extractNetconfServerAddress(bundleContext, prop);
- assertTrue(inetSocketAddressOptional.isPresent());
- assertEquals(inetSocketAddressOptional.get(), new InetSocketAddress("1.1.1.1", 20));
}
@Test
- public void testGetPrivateKeyPath() throws Exception {
- doReturn("path").when(bundleContext).getProperty("netconf.ssh.pk.path");
- assertEquals(NetconfConfigUtil.getPrivateKeyPath(bundleContext).get(), "path");
+ public void testGetNetconfConfigurationServiceAbsent() throws Exception {
+ final Collection<ServiceReference<ManagedService>> services = new ArrayList<>();
+ services.add(serviceRef);
+ final BundleContext context = mock(BundleContext.class);
+ doReturn(services).when(context).getServiceReferences(ManagedService.class, null);
+ final ManagedService service = mock(ManagedService.class);
+ doReturn(service).when(context).getService(serviceRef);
+ final java.util.Optional<NetconfConfiguration> netconfConfigurationOptional =
+ NetconfConfigUtil.getNetconfConfigurationService(context);
+ Assert.assertFalse(netconfConfigurationOptional.isPresent());
}
@Test
- public void testGetPrivateKeyPathNotPresent() throws Exception {
- doReturn(null).when(bundleContext).getProperty("netconf.ssh.pk.path");
- assertEquals(NetconfConfigUtil.getPrivateKeyPath(bundleContext), Optional.absent());
+ public void testGetNetconfConfigurationServiceInvalidSyntax() throws Exception {
+ final BundleContext context = mock(BundleContext.class);
+ final InvalidSyntaxException exception = new InvalidSyntaxException("Invalid syntax", "filter");
+ doThrow(exception).when(context).getServiceReferences(ManagedService.class, null);
+ final java.util.Optional<NetconfConfiguration> netconfConfigurationOptional =
+ NetconfConfigUtil.getNetconfConfigurationService(context);
+ Assert.assertFalse(netconfConfigurationOptional.isPresent());
}
}
--- /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.util.osgi;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ManagedService;
+
+public class NetconfConfigurationActivatorTest {
+
+ @Mock
+ private BundleContext context;
+ @Mock
+ private ServiceRegistration registration;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ doReturn(registration).when(context)
+ .registerService(eq(ManagedService.class), any(NetconfConfiguration.class), any());
+ doNothing().when(registration).unregister();
+ }
+
+ @Test
+ public void testStartStop() throws Exception {
+ final NetconfConfigurationActivator activator = new NetconfConfigurationActivator();
+ activator.start(context);
+ final Dictionary<String, String> props = new Hashtable<>();
+ props.put(Constants.SERVICE_PID, "netconf");
+ verify(context).registerService(eq(ManagedService.class), eq(NetconfConfiguration.getInstance()), eq(props));
+ activator.stop(context);
+ verify(registration).unregister();
+ }
+
+}
\ No newline at end of file
--- /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.util.osgi;
+
+import io.netty.channel.local.LocalAddress;
+import java.net.InetSocketAddress;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class NetconfConfigurationTest {
+
+ @Test
+ public void testUpdated() throws Exception {
+ final NetconfConfiguration config = NetconfConfiguration.getInstance();
+ Assert.assertEquals(new InetSocketAddress("0.0.0.0", 1830), config.getSshServerAddress());
+ Assert.assertEquals(new InetSocketAddress("127.0.0.1", 8383), config.getTcpServerAddress());
+ Assert.assertEquals("./configuration/RSA.pk", config.getPrivateKeyPath());
+ final Dictionary<String, String> newValues = new Hashtable<>();
+ final String newSshIp = "192.168.1.1";
+ final String newTcpIp = "192.168.1.2";
+ final int newSshPort = 1234;
+ final int newTcpPort = 4567;
+ final String newSshKeyPath = "./new_folder/configuration/RSA.pk";
+ newValues.put("ssh-address", newSshIp);
+ newValues.put("ssh-port", Integer.toString(newSshPort));
+ newValues.put("tcp-address", newTcpIp);
+ newValues.put("tcp-port", Integer.toString(newTcpPort));
+ newValues.put("ssh-pk-path", newSshKeyPath);
+ config.updated(newValues);
+ Assert.assertEquals(new InetSocketAddress(newSshIp, newSshPort), config.getSshServerAddress());
+ Assert.assertEquals(new InetSocketAddress(newTcpIp, newTcpPort), config.getTcpServerAddress());
+ Assert.assertEquals(newSshKeyPath, config.getPrivateKeyPath());
+ }
+
+ @Test
+ public void testUpdatedNull() throws Exception {
+ final NetconfConfiguration config = NetconfConfiguration.getInstance();
+ Assert.assertEquals(new InetSocketAddress("0.0.0.0", 1830), config.getSshServerAddress());
+ Assert.assertEquals(new InetSocketAddress("127.0.0.1", 8383), config.getTcpServerAddress());
+ Assert.assertEquals("./configuration/RSA.pk", config.getPrivateKeyPath());
+ final Dictionary<String, String> nullDictionary = null;
+ config.updated(nullDictionary);
+ Assert.assertEquals(new InetSocketAddress("0.0.0.0", 1830), config.getSshServerAddress());
+ Assert.assertEquals(new InetSocketAddress("127.0.0.1", 8383), config.getTcpServerAddress());
+ Assert.assertEquals("./configuration/RSA.pk", config.getPrivateKeyPath());
+ }
+}
\ No newline at end of file
import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
import org.opendaylight.netconf.sal.connect.netconf.sal.tx.ReadOnlyTx;
import org.opendaylight.netconf.sal.connect.netconf.sal.tx.ReadWriteTx;
+import org.opendaylight.netconf.sal.connect.netconf.sal.tx.TxChain;
import org.opendaylight.netconf.sal.connect.netconf.sal.tx.WriteCandidateRunningTx;
import org.opendaylight.netconf.sal.connect.netconf.sal.tx.WriteCandidateTx;
import org.opendaylight.netconf.sal.connect.netconf.sal.tx.WriteRunningTx;
private final NetconfBaseOps netconfOps;
private final boolean rollbackSupport;
- private boolean candidateSupported;
- private boolean runningWritable;
+ private final boolean candidateSupported;
+ private final boolean runningWritable;
public NetconfDeviceDataBroker(final RemoteDeviceId id, final SchemaContext schemaContext, final DOMRpcService rpc, final NetconfSessionPreferences netconfSessionPreferences) {
this.id = id;
@Override
public DOMTransactionChain createTransactionChain(final TransactionChainListener listener) {
- throw new UnsupportedOperationException(id + ": Transaction chains not supported for netconf mount point");
+ return new TxChain(this, listener);
}
@Override
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import javax.annotation.Nullable;
import org.opendaylight.controller.config.util.xml.DocumentedException;
import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
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.api.DOMDataWriteTransaction;
import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
import org.opendaylight.netconf.api.NetconfDocumentedException;
import org.opendaylight.netconf.sal.connect.netconf.util.NetconfBaseOps;
import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
+import org.opendaylight.yangtools.yang.common.RpcError;
import org.opendaylight.yangtools.yang.common.RpcResult;
import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
import org.opendaylight.yangtools.yang.data.api.ModifyAction;
protected final NetconfBaseOps netOps;
protected final boolean rollbackSupport;
protected final List<ListenableFuture<DOMRpcResult>> resultsFutures;
+ private final List<TxListener> listeners = new CopyOnWriteArrayList<>();
// Allow commit to be called only once
protected boolean finished = false;
if(isFinished()) {
return false;
}
-
+ listeners.forEach(listener -> listener.onTransactionCancelled(this));
finished = true;
cleanup();
return true;
@Override
public final ListenableFuture<RpcResult<TransactionStatus>> commit() {
+ listeners.forEach(listener -> listener.onTransactionSubmitted(this));
checkNotFinished();
finished = true;
+ final ListenableFuture<RpcResult<TransactionStatus>> result = performCommit();
+ Futures.addCallback(result, new FutureCallback<RpcResult<TransactionStatus>>() {
+ @Override
+ public void onSuccess(@Nullable final RpcResult<TransactionStatus> result) {
+ if (result != null && result.isSuccessful()) {
+ listeners.forEach(txListener -> txListener.onTransactionSuccessful(AbstractWriteTx.this));
+ } else {
+ final TransactionCommitFailedException cause = new TransactionCommitFailedException("Transaction failed", result.getErrors().toArray(new RpcError[result.getErrors().size()]));
+ listeners.forEach(listener -> listener.onTransactionFailed(AbstractWriteTx.this, cause));
+ }
+ }
- return performCommit();
+ @Override
+ public void onFailure(final Throwable t) {
+ listeners.forEach(listener -> listener.onTransactionFailed(AbstractWriteTx.this, t));
+ }
+ });
+ return result;
}
protected abstract ListenableFuture<RpcResult<TransactionStatus>> performCommit();
public void onFailure(final Throwable throwable) {
final NetconfDocumentedException exception =
new NetconfDocumentedException(
- new DocumentedException(id + ":RPC during tx returned an exception",
- new Exception(throwable),
- DocumentedException.ErrorType.APPLICATION,
- DocumentedException.ErrorTag.OPERATION_FAILED,
- DocumentedException.ErrorSeverity.ERROR));
+ id + ":RPC during tx returned an exception",
+ new Exception(throwable),
+ DocumentedException.ErrorType.APPLICATION,
+ DocumentedException.ErrorTag.OPERATION_FAILED,
+ DocumentedException.ErrorSeverity.ERROR);
transformed.setException(exception);
}
});
return transformed;
}
+
+ AutoCloseable addListener(final TxListener listener) {
+ listeners.add(listener);
+ return () -> listeners.remove(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.sal.connect.netconf.sal.tx;
+
+import com.google.common.base.Preconditions;
+import java.util.HashMap;
+import java.util.Map;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionChainClosedException;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionChainListener;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link DOMTransactionChain} implementation for Netconf connector.
+ */
+public class TxChain implements DOMTransactionChain, TxListener {
+
+ private static final Logger LOG = LoggerFactory.getLogger(TxChain.class);
+
+ private final DOMDataBroker dataBroker;
+ private final TransactionChainListener listener;
+ /**
+ * Submitted transactions that haven't completed yet.
+ */
+ private final Map<DOMDataWriteTransaction, AutoCloseable> pendingTransactions = new HashMap<>();
+
+ /**
+ * Transaction created by this chain that hasn't been submitted or cancelled yet.
+ */
+ private AbstractWriteTx currentTransaction = null;
+ private boolean closed = false;
+ private boolean successful = true;
+
+ public TxChain(final DOMDataBroker dataBroker, final TransactionChainListener listener) {
+ this.dataBroker = dataBroker;
+ this.listener = listener;
+ }
+
+ @Override
+ public synchronized DOMDataReadOnlyTransaction newReadOnlyTransaction() {
+ checkOperationPermitted();
+ return dataBroker.newReadOnlyTransaction();
+ }
+
+ @Override
+ public synchronized AbstractWriteTx newWriteOnlyTransaction() {
+ checkOperationPermitted();
+ final DOMDataWriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
+ Preconditions.checkState(writeTransaction instanceof AbstractWriteTx);
+ final AbstractWriteTx pendingWriteTx = (AbstractWriteTx) writeTransaction;
+ pendingTransactions.put(pendingWriteTx, pendingWriteTx.addListener(this));
+ currentTransaction = pendingWriteTx;
+ return pendingWriteTx;
+ }
+
+ @Override
+ public synchronized DOMDataReadWriteTransaction newReadWriteTransaction() {
+ return new ReadWriteTx(dataBroker.newReadOnlyTransaction(), newWriteOnlyTransaction());
+ }
+
+ @Override
+ public synchronized void close() {
+ if (!closed) {
+ closed = true;
+ notifyChainListenerSuccess();
+ }
+ }
+
+ @Override
+ public synchronized void onTransactionSuccessful(final AbstractWriteTx transaction) {
+ removePendingTx(transaction);
+ notifyChainListenerSuccess();
+ }
+
+ @Override
+ public synchronized void onTransactionFailed(final AbstractWriteTx transaction, final Throwable cause) {
+ removePendingTx(transaction);
+ successful = false;
+ if (currentTransaction != null) {
+ currentTransaction.cancel();
+ }
+ listener.onTransactionChainFailed(this, transaction, cause);
+ }
+
+ @Override
+ public synchronized void onTransactionSubmitted(final AbstractWriteTx transaction) {
+ currentTransaction = null;
+ }
+
+ @Override
+ public synchronized void onTransactionCancelled(final AbstractWriteTx transaction) {
+ removePendingTx(transaction);
+ currentTransaction = null;
+ }
+
+ private void removePendingTx(final AbstractWriteTx transaction) {
+ try {
+ pendingTransactions.remove(transaction).close();
+ } catch (final Exception e) {
+ LOG.error("Can't remove transaction listener registration", e);
+ }
+ }
+
+ /**
+ * Checks, if chain isn't closed and if there is no not submitted write transaction waiting.
+ */
+ private void checkOperationPermitted() {
+ if (closed) {
+ throw new TransactionChainClosedException("Transaction chain was closed");
+ }
+ Preconditions.checkState(currentTransaction == null, "Last write transaction has not finished yet");
+ }
+
+ private void notifyChainListenerSuccess() {
+ if (closed && pendingTransactions.isEmpty() && successful) {
+ listener.onTransactionChainSuccessful(this);
+ }
+ }
+
+}
--- /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.sal.connect.netconf.sal.tx;
+
+interface TxListener {
+
+ /**
+ * Invoked, when transaction completes successfully.
+ * @param transaction transaction
+ */
+ void onTransactionSuccessful(AbstractWriteTx transaction);
+
+ /**
+ * Invoked, when transaction fails.
+ *
+ * @param transaction transaction
+ * @param cause cause
+ */
+ void onTransactionFailed(AbstractWriteTx transaction, Throwable cause);
+
+ /**
+ * Invoked, when transaction is cancelled.
+ * @param transaction transaction
+ */
+ void onTransactionCancelled(AbstractWriteTx transaction);
+
+ /**
+ * Invoked, when transaction is submitted.
+ * @param transaction transaction
+ */
+ void onTransactionSubmitted(AbstractWriteTx transaction);
+
+
+}
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_CANDIDATE_QNAME;
import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_FILTER_QNAME;
import com.google.common.util.concurrent.CheckedFuture;
import com.google.common.util.concurrent.Futures;
import java.net.InetSocketAddress;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
inOrder.verify(rpc).invokeRpc(toPath(NetconfMessageTransformUtil.NETCONF_UNLOCK_QNAME), NetconfBaseOps.getUnLockContent(NETCONF_RUNNING_QNAME));
}
+ @Test
+ public void testListenerSuccess() throws Exception {
+ doReturn(Futures.immediateCheckedFuture(new DefaultDOMRpcResult((NormalizedNode<?, ?>) null)))
+ .when(rpc).invokeRpc(any(SchemaPath.class), any(NormalizedNode.class));
+ final WriteCandidateTx tx = new WriteCandidateTx(id, new NetconfBaseOps(rpc, BaseSchema.BASE_NETCONF_CTX.getSchemaContext()), false);
+ final TxListener listener = mock(TxListener.class);
+ tx.addListener(listener);
+ tx.delete(LogicalDatastoreType.CONFIGURATION, yangIId);
+ tx.submit();
+ verify(listener).onTransactionSubmitted(tx);
+ verify(listener).onTransactionSuccessful(tx);
+ verify(listener, never()).onTransactionFailed(eq(tx), any());
+ verify(listener, never()).onTransactionCancelled(tx);
+ }
+
+ @Test
+ public void testListenerCancellation() throws Exception {
+ doReturn(Futures.immediateCheckedFuture(new DefaultDOMRpcResult((NormalizedNode<?, ?>) null)))
+ .when(rpc).invokeRpc(any(SchemaPath.class), any(NormalizedNode.class));
+ final WriteCandidateTx tx = new WriteCandidateTx(id, new NetconfBaseOps(rpc, BaseSchema.BASE_NETCONF_CTX.getSchemaContext()), false);
+ final TxListener listener = mock(TxListener.class);
+ tx.addListener(listener);
+ tx.delete(LogicalDatastoreType.CONFIGURATION, yangIId);
+ tx.cancel();
+ verify(listener).onTransactionCancelled(tx);
+ verify(listener, never()).onTransactionSubmitted(tx);
+ verify(listener, never()).onTransactionSuccessful(tx);
+ verify(listener, never()).onTransactionFailed(eq(tx), any());
+ }
+
+ @Test
+ public void testListenerFailure() throws Exception {
+ final IllegalStateException cause = new IllegalStateException("Failed tx");
+ doReturn(Futures.immediateFailedCheckedFuture(cause))
+ .when(rpc).invokeRpc(any(SchemaPath.class), any(NormalizedNode.class));
+ final WriteCandidateTx tx = new WriteCandidateTx(id, new NetconfBaseOps(rpc, BaseSchema.BASE_NETCONF_CTX.getSchemaContext()), false);
+ final TxListener listener = mock(TxListener.class);
+ tx.addListener(listener);
+ tx.delete(LogicalDatastoreType.CONFIGURATION, yangIId);
+ tx.submit();
+ final ArgumentCaptor<Exception> excCaptor = ArgumentCaptor.forClass(Exception.class);
+ verify(listener).onTransactionSubmitted(tx);
+ verify(listener).onTransactionFailed(eq(tx), excCaptor.capture());
+ Assert.assertEquals(cause, excCaptor.getValue().getCause().getCause());
+ verify(listener, never()).onTransactionSuccessful(tx);
+ verify(listener, never()).onTransactionCancelled(tx);
+ }
}
--- /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.sal.connect.netconf.sal.tx;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionChainClosedException;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionChainListener;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
+
+public class TxChainTest {
+
+ @Mock
+ private DOMDataBroker broker;
+ @Mock
+ private TransactionChainListener listener;
+ @Mock
+ private DOMDataReadOnlyTransaction readOnlyTx;
+ @Mock
+ private AbstractWriteTx writeOnlyTx1;
+ @Mock
+ private AbstractWriteTx writeOnlyTx2;
+ @Mock
+ private AbstractWriteTx writeOnlyTx3;
+ @Mock
+ private AutoCloseable registration1;
+ @Mock
+ private AutoCloseable registration2;
+ @Mock
+ private AutoCloseable registration3;
+ private final ArgumentCaptor<TxListener> captor = ArgumentCaptor.forClass(TxListener.class);
+ private TxChain chain;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ when(broker.newReadOnlyTransaction()).thenReturn(readOnlyTx);
+ when(broker.newWriteOnlyTransaction()).thenReturn(writeOnlyTx1).thenReturn(writeOnlyTx2).thenReturn(writeOnlyTx3);
+ when(writeOnlyTx1.addListener(any())).thenReturn(registration1);
+ when(writeOnlyTx2.addListener(any())).thenReturn(registration2);
+ when(writeOnlyTx3.addListener(any())).thenReturn(registration3);
+ chain = new TxChain(broker, listener);
+ }
+
+ @Test()
+ public void testNewReadOnlyTransactionPrevSubmitted() throws Exception {
+ chain.newWriteOnlyTransaction();
+ verify(writeOnlyTx1).addListener(captor.capture());
+ captor.getValue().onTransactionSubmitted(writeOnlyTx1);
+ chain.newReadOnlyTransaction();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNewReadOnlyTransactionPrevNotSubmitted() throws Exception {
+ chain.newWriteOnlyTransaction();
+ chain.newReadOnlyTransaction();
+ }
+
+ @Test
+ public void testNewReadWriteTransactionPrevSubmitted() throws Exception {
+ chain.newReadWriteTransaction();
+ verify(writeOnlyTx1).addListener(captor.capture());
+ captor.getValue().onTransactionSubmitted(writeOnlyTx1);
+ chain.newReadWriteTransaction();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNewReadWriteTransactionPrevNotSubmitted() throws Exception {
+ chain.newReadWriteTransaction();
+ chain.newReadWriteTransaction();
+ }
+
+ @Test
+ public void testNewWriteOnlyTransactionPrevSubmitted() throws Exception {
+ chain.newWriteOnlyTransaction();
+ verify(writeOnlyTx1).addListener(captor.capture());
+ captor.getValue().onTransactionSubmitted(writeOnlyTx1);
+ chain.newWriteOnlyTransaction();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNewWriteOnlyTransactionPrevNotSubmitted() throws Exception {
+ chain.newWriteOnlyTransaction();
+ chain.newWriteOnlyTransaction();
+ }
+
+ @Test(expected = TransactionChainClosedException.class)
+ public void testCloseAfterFinished() throws Exception {
+ chain.close();
+ verify(listener).onTransactionChainSuccessful(chain);
+ chain.newReadOnlyTransaction();
+ }
+
+ @Test
+ public void testChainFail() throws Exception {
+ final AbstractWriteTx writeTx = chain.newWriteOnlyTransaction();
+ final ArgumentCaptor<TxListener> captor = ArgumentCaptor.forClass(TxListener.class);
+ verify(writeOnlyTx1).addListener(captor.capture());
+ writeTx.submit();
+ final TransactionCommitFailedException cause = new TransactionCommitFailedException("fail");
+ captor.getValue().onTransactionFailed(writeOnlyTx1, cause);
+ verify(registration1).close();
+ verify(listener).onTransactionChainFailed(chain, writeOnlyTx1, cause);
+ }
+
+ @Test
+ public void testChainSuccess() throws Exception {
+ final AbstractWriteTx writeTx = chain.newWriteOnlyTransaction();
+ chain.close();
+ verify(writeOnlyTx1).addListener(captor.capture());
+ writeTx.submit();
+ captor.getValue().onTransactionSuccessful(writeOnlyTx1);
+ verify(registration1).close();
+ verify(listener).onTransactionChainSuccessful(chain);
+ }
+
+ @Test
+ public void testCancel() throws Exception {
+ final AbstractWriteTx writeTx = chain.newWriteOnlyTransaction();
+ verify(writeOnlyTx1).addListener(captor.capture());
+ writeTx.cancel();
+ captor.getValue().onTransactionCancelled(writeOnlyTx1);
+ chain.newWriteOnlyTransaction();
+ }
+
+ @Test
+ public void testMultiplePendingTransactions() throws Exception {
+ //create 1st tx
+ final AbstractWriteTx writeTx1 = chain.newWriteOnlyTransaction();
+ final ArgumentCaptor<TxListener> captor1 = ArgumentCaptor.forClass(TxListener.class);
+ verify(writeOnlyTx1).addListener(captor1.capture());
+ //submit 1st tx
+ writeTx1.submit();
+ captor1.getValue().onTransactionSubmitted(writeOnlyTx1);
+
+ //create 2nd tx
+ final AbstractWriteTx writeTx2 = chain.newWriteOnlyTransaction();
+ final ArgumentCaptor<TxListener> captor2 = ArgumentCaptor.forClass(TxListener.class);
+ verify(writeTx2).addListener(captor2.capture());
+ //submit 2nd tx
+ writeTx2.submit();
+ captor2.getValue().onTransactionSubmitted(writeOnlyTx2);
+
+ //create 3rd tx
+ final AbstractWriteTx writeTx3 = chain.newWriteOnlyTransaction();
+ final ArgumentCaptor<TxListener> captor3 = ArgumentCaptor.forClass(TxListener.class);
+ verify(writeTx3).addListener(captor3.capture());
+ //cancel 3rd tx
+ writeTx3.cancel();
+ captor3.getValue().onTransactionCancelled(writeOnlyTx3);
+
+ //close chain
+ chain.close();
+
+ //complete first two transactions successfully
+ captor1.getValue().onTransactionSuccessful(writeOnlyTx1);
+ captor2.getValue().onTransactionSuccessful(writeOnlyTx2);
+
+ verify(registration1).close();
+ verify(registration2).close();
+ verify(registration3).close();
+ verify(listener).onTransactionChainSuccessful(chain);
+ }
+
+ @Test
+ public void testMultiplePendingTransactionsFail() throws Exception {
+ //create 1st tx
+ final AbstractWriteTx writeTx1 = chain.newWriteOnlyTransaction();
+ final ArgumentCaptor<TxListener> captor1 = ArgumentCaptor.forClass(TxListener.class);
+ verify(writeOnlyTx1).addListener(captor1.capture());
+ //submit 1st tx
+ writeTx1.submit();
+ captor1.getValue().onTransactionSubmitted(writeOnlyTx1);
+
+ //create 2nd tx
+ final AbstractWriteTx writeTx2 = chain.newWriteOnlyTransaction();
+ final ArgumentCaptor<TxListener> captor2 = ArgumentCaptor.forClass(TxListener.class);
+ verify(writeTx2).addListener(captor2.capture());
+ //submit 2nd tx
+ writeTx2.submit();
+ captor2.getValue().onTransactionSubmitted(writeOnlyTx2);
+
+ //create 3rd tx
+ final AbstractWriteTx writeTx3 = chain.newWriteOnlyTransaction();
+ final ArgumentCaptor<TxListener> captor3 = ArgumentCaptor.forClass(TxListener.class);
+ verify(writeTx3).addListener(captor3.capture());
+
+ chain.close();
+
+ //fail 1st transaction
+ final Exception cause1 = new Exception("fail");
+ captor1.getValue().onTransactionFailed(writeOnlyTx1, cause1);
+ //current unsubmitted transaction should be cancelled
+ verify(writeTx3).cancel();
+ captor3.getValue().onTransactionCancelled(writeTx3);
+ //2nd transaction success
+ captor2.getValue().onTransactionSuccessful(writeOnlyTx2);
+
+ verify(registration1).close();
+ verify(registration2).close();
+ verify(registration3).close();
+ verify(listener).onTransactionChainFailed(chain, writeOnlyTx1, cause1);
+ // 1 transaction failed, onTransactionChainSuccessful must not be called
+ verify(listener, never()).onTransactionChainSuccessful(chain);
+ }
+}
\ No newline at end of file
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.HashedWheelTimer;
import java.io.Closeable;
-import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.BindException;
import org.opendaylight.netconf.ssh.SshProxyServer;
import org.opendaylight.netconf.ssh.SshProxyServerConfiguration;
import org.opendaylight.netconf.ssh.SshProxyServerConfigurationBuilder;
+import org.opendaylight.netconf.test.tool.customrpc.SettableOperationProvider;
import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
import org.opendaylight.yangtools.yang.model.api.Module;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
this.nioExecutor = nioExecutor;
}
- private NetconfServerDispatcherImpl createDispatcher(final Set<Capability> capabilities, final boolean exi, final int generateConfigsTimeout,
- final Optional<File> notificationsFile, final boolean mdSal, final Optional<File> initialConfigXMLFile,
- final SchemaSourceProvider<YangTextSchemaSource> sourceProvider) {
+ private NetconfServerDispatcherImpl createDispatcher(final Set<Capability> capabilities,
+ final SchemaSourceProvider<YangTextSchemaSource> sourceProvider,
+ final TesttoolParameters params) {
final Set<Capability> transformedCapabilities = Sets.newHashSet(Collections2.transform(capabilities, new Function<Capability, Capability>() {
@Override
}
}
}));
-
- final SessionIdProvider idProvider = new SessionIdProvider();
-
- final AggregatedNetconfOperationServiceFactory aggregatedNetconfOperationServiceFactory = new AggregatedNetconfOperationServiceFactory();
- final NetconfOperationServiceFactory operationProvider = mdSal ? new MdsalOperationProvider(idProvider, transformedCapabilities, schemaContext, sourceProvider) :
- new SimulatedOperationProvider(idProvider, transformedCapabilities, notificationsFile, initialConfigXMLFile);
-
transformedCapabilities.add(new BasicCapability("urn:ietf:params:netconf:capability:candidate:1.0"));
-
final NetconfMonitoringService monitoringService1 = new DummyMonitoringService(transformedCapabilities);
+ final SessionIdProvider idProvider = new SessionIdProvider();
- final NetconfMonitoringActivator.NetconfMonitoringOperationServiceFactory monitoringService =
- new NetconfMonitoringActivator.NetconfMonitoringOperationServiceFactory(
- new NetconfMonitoringOperationService(monitoringService1));
- aggregatedNetconfOperationServiceFactory.onAddNetconfOperationServiceFactory(operationProvider);
- aggregatedNetconfOperationServiceFactory.onAddNetconfOperationServiceFactory(monitoringService);
+ final NetconfOperationServiceFactory aggregatedNetconfOperationServiceFactory = createOperationServiceFactory(sourceProvider, params, transformedCapabilities, monitoringService1, idProvider);
- final Set<String> serverCapabilities = exi
+ final Set<String> serverCapabilities = params.exi
? NetconfServerSessionNegotiatorFactory.DEFAULT_BASE_CAPABILITIES
: Sets.newHashSet(XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_0, XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_1);
final NetconfServerSessionNegotiatorFactory serverNegotiatorFactory = new TesttoolNegotiationFactory(
- hashedWheelTimer, aggregatedNetconfOperationServiceFactory, idProvider, generateConfigsTimeout, monitoringService1, serverCapabilities);
+ hashedWheelTimer, aggregatedNetconfOperationServiceFactory, idProvider, params.generateConfigsTimeout, monitoringService1, serverCapabilities);
final NetconfServerDispatcherImpl.ServerChannelInitializer serverChannelInitializer = new NetconfServerDispatcherImpl.ServerChannelInitializer(
serverNegotiatorFactory);
return new NetconfServerDispatcherImpl(serverChannelInitializer, nettyThreadgroup, nettyThreadgroup);
}
+ private NetconfOperationServiceFactory createOperationServiceFactory(final SchemaSourceProvider<YangTextSchemaSource> sourceProvider,
+ final TesttoolParameters params,
+ final Set<Capability> transformedCapabilities,
+ final NetconfMonitoringService monitoringService1,
+ final SessionIdProvider idProvider) {
+ final AggregatedNetconfOperationServiceFactory aggregatedNetconfOperationServiceFactory = new AggregatedNetconfOperationServiceFactory();
+
+ final NetconfOperationServiceFactory operationProvider;
+ if (params.mdSal) {
+ operationProvider = new MdsalOperationProvider(idProvider, transformedCapabilities, schemaContext, sourceProvider);
+ } else {
+ operationProvider = new SimulatedOperationProvider(idProvider, transformedCapabilities,
+ Optional.fromNullable(params.notificationFile),
+ Optional.fromNullable(params.initialConfigXMLFile));
+ }
+
+
+ final NetconfMonitoringActivator.NetconfMonitoringOperationServiceFactory monitoringService =
+ new NetconfMonitoringActivator.NetconfMonitoringOperationServiceFactory(
+ new NetconfMonitoringOperationService(monitoringService1));
+ aggregatedNetconfOperationServiceFactory.onAddNetconfOperationServiceFactory(operationProvider);
+ aggregatedNetconfOperationServiceFactory.onAddNetconfOperationServiceFactory(monitoringService);
+ if (params.rpcConfig != null) {
+ final SettableOperationProvider settableService = new SettableOperationProvider(params.rpcConfig);
+ aggregatedNetconfOperationServiceFactory.onAddNetconfOperationServiceFactory(settableService);
+ }
+ return aggregatedNetconfOperationServiceFactory;
+ }
+
public List<Integer> start(final TesttoolParameters params) {
LOG.info("Starting {}, {} simulated devices starting on port {}", params.deviceCount, params.ssh ? "SSH" : "TCP", params.startingPort);
final SharedSchemaRepository schemaRepo = new SharedSchemaRepository("netconf-simulator");
final Set<Capability> capabilities = parseSchemasToModuleCapabilities(params, schemaRepo);
- final NetconfServerDispatcherImpl dispatcher = createDispatcher(capabilities, params.exi, params.generateConfigsTimeout,
- Optional.fromNullable(params.notificationFile), params.mdSal, Optional.fromNullable(params.initialConfigXMLFile),
+ final NetconfServerDispatcherImpl dispatcher = createDispatcher(capabilities,
new SchemaSourceProvider<YangTextSchemaSource>() {
@Override
public CheckedFuture<? extends YangTextSchemaSource, SchemaSourceException> getSource(final SourceIdentifier sourceIdentifier) {
return schemaRepo.getSchemaSource(sourceIdentifier, YangTextSchemaSource.class);
}
- });
+ }, params);
int currentPort = params.startingPort;
@Arg(dest = "thread-pool-size")
public int threadPoolSize;
+ @Arg(dest = "rpc-config")
+ public File rpcConfig;
static ArgumentParser getParser() {
final ArgumentParser parser = ArgumentParsers.newArgumentParser("netconf testtool");
.setDefault(8)
.help("The number of threads to keep in the pool, when creating a device simulator. Even if they are idle.")
.dest("thread-pool-size");
+ parser.addArgument("--rpc-config")
+ .type(File.class)
+ .help("Rpc config file. It can be used to define custom rpc behavior, or override the default one." +
+ "Usable for testing buggy device behavior.")
+ .dest("rpc-config");
return parser;
}
}
}
}
+ if (rpcConfig != null) {
+ checkArgument(rpcConfig.exists(), "Rpc config file has to exist");
+ checkArgument(!rpcConfig.isDirectory(), "Rpc config file can't be a directory");
+ checkArgument(rpcConfig.canRead(), "Rpc config file to be readable");
+ }
}
public ArrayList<ArrayList<Execution.DestToPayload>> getThreadsPayloads(final List<Integer> openDevices) {
--- /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.test.tool.customrpc;
+
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+
+class Rpc {
+
+ @XmlElement(name = "input")
+ private XmlData input;
+
+ @XmlElement(name = "output")
+ private List<XmlData> output;
+
+ XmlData getInput() {
+ return input;
+ }
+
+ List<XmlData> getOutput() {
+ return output;
+ }
+}
--- /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.test.tool.customrpc;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
+import java.io.File;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Stream;
+import javax.xml.bind.JAXB;
+import org.opendaylight.controller.config.util.xml.DocumentedException;
+import org.opendaylight.controller.config.util.xml.XmlElement;
+import org.opendaylight.controller.config.util.xml.XmlUtil;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+
+/**
+ * Mapping between RPCs and responses.
+ */
+class RpcMapping {
+
+ private final Multimap<Request, Document> requestToResponseMap = ArrayListMultimap.create();
+
+ /**
+ * Creates new mapping from file.
+ *
+ * @param config config file
+ */
+ RpcMapping(final File config) {
+ final Rpcs rpcs = JAXB.unmarshal(config, Rpcs.class);
+ for (final Rpc rpc : rpcs.getRpcs()) {
+ final Stream<Document> stream = rpc.getOutput().stream()
+ .map(XmlData::getData);
+ final XmlElement element = XmlElement.fromDomDocument(rpc.getInput().getData());
+ requestToResponseMap.putAll(new Request(element), stream::iterator);
+ }
+ }
+
+ /**
+ * Returns response matching given input. If multiple responses are configured for the same
+ * request, every invocation of this method returns next response as they are defined in the config file.
+ * Last configured response is used, when number of invocations is higher than number of configured responses
+ * for rpc.
+ *
+ * @param input request
+ * @return response document, or absent if mapping is not defined
+ */
+ Optional<Document> getResponse(final XmlElement input) {
+ final Collection<Document> responses = requestToResponseMap.get(new Request(input));
+ final Iterator<Document> iterator = responses.iterator();
+ if (iterator.hasNext()) {
+ final Document response = iterator.next();
+ if (iterator.hasNext()) {
+ iterator.remove();
+ }
+ return Optional.of(response);
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * Rpc input wrapper. Needed because of custom {@link Request#equals(Object)}
+ * and {@link Request#hashCode()} implementations.
+ */
+ private static class Request {
+ private final XmlElement xmlElement;
+ private final int hashCode;
+
+ private Request(final XmlElement element) {
+ this.xmlElement = element;
+ hashCode = XmlUtil.toString(element)
+ .replaceAll("message-id=.*(>| )", "") //message id is variable, remove it
+ .replaceAll("\\s+", "") //remove whitespaces
+ .hashCode();
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final Request request = (Request) o;
+ return documentEquals(this.xmlElement, request.xmlElement);
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ private static boolean documentEquals(final XmlElement e1, final XmlElement e2) {
+ if (!e1.getNamespaceOptionally().equals(e2.getNamespaceOptionally())) {
+ return false;
+ }
+ if (!e1.getName().equals(e2.getName())) {
+ return false;
+ }
+ if (attributesNotEquals(e1, e2)) {
+ return false;
+ }
+
+ final List<XmlElement> e1Children = e1.getChildElements();
+ final List<XmlElement> e2Children = e2.getChildElements();
+ if (e1Children.size() != e2Children.size()) {
+ return false;
+ }
+ final Iterator<XmlElement> e1Iterator = e1Children.iterator();
+ final Iterator<XmlElement> e2Iterator = e2Children.iterator();
+ while (e1Iterator.hasNext() && e2Iterator.hasNext()) {
+ if (!documentEquals(e1Iterator.next(), e2Iterator.next())) {
+ return false;
+ }
+ }
+
+ if (e1Children.isEmpty() && e1Children.isEmpty()) {
+ try {
+ return e1.getTextContent().equals(e2.getTextContent());
+ } catch (final DocumentedException e) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean attributesNotEquals(final XmlElement e1, final XmlElement e2) {
+ final Map<String, Attr> e1Attrs = e1.getAttributes();
+ final Map<String, Attr> e2Attrs = e2.getAttributes();
+ final Iterator<Map.Entry<String, Attr>> e1AttrIt = e1Attrs.entrySet().iterator();
+ final Iterator<Map.Entry<String, Attr>> e2AttrIt = e2Attrs.entrySet().iterator();
+ while (e1AttrIt.hasNext() && e2AttrIt.hasNext()) {
+ final Map.Entry<String, Attr> e1Next = e1AttrIt.next();
+ final Map.Entry<String, Attr> e2Next = e2AttrIt.next();
+ if (e1Next.getKey().equals("message-id") && e2Next.getKey().equals("message-id")) {
+ continue;
+ }
+ if (e1Next.getKey().equals("xmlns") && e2Next.getKey().equals("xmlns")) {
+ continue;
+ }
+ if (!e1Next.getKey().equals(e2Next.getKey())) {
+ return true;
+ }
+ if (!e1Next.getValue().getValue().equals(e2Next.getValue().getValue())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ }
+}
--- /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.test.tool.customrpc;
+
+import java.util.List;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement(name = "rpcs")
+@XmlAccessorType(XmlAccessType.FIELD)
+class Rpcs {
+
+ @XmlElement(name = "rpc")
+ private List<Rpc> rpcs;
+
+ List<Rpc> getRpcs() {
+ return rpcs;
+ }
+
+}
--- /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.test.tool.customrpc;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Set;
+import org.opendaylight.controller.config.util.capability.Capability;
+import org.opendaylight.netconf.api.monitoring.CapabilityListener;
+import org.opendaylight.netconf.mapping.api.NetconfOperation;
+import org.opendaylight.netconf.mapping.api.NetconfOperationService;
+import org.opendaylight.netconf.mapping.api.NetconfOperationServiceFactory;
+
+public class SettableOperationProvider implements NetconfOperationServiceFactory {
+
+ private final File rpcConfig;
+
+ public SettableOperationProvider(final File rpcConfig) {
+ this.rpcConfig = rpcConfig;
+ }
+
+ @Override
+ public Set<Capability> getCapabilities() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public AutoCloseable registerCapabilityListener(final CapabilityListener listener) {
+ return () -> {
+ //no op
+ };
+ }
+
+ @Override
+ public NetconfOperationService createService(final String netconfSessionIdForReporting) {
+ return new SettableOperationService(rpcConfig);
+ }
+
+ private static class SettableOperationService implements NetconfOperationService {
+
+ private final SettableRpc rpc;
+
+ private SettableOperationService(final File rpcConfig) {
+ this.rpc = new SettableRpc(rpcConfig);
+ }
+
+ @Override
+ public Set<NetconfOperation> getNetconfOperations() {
+ return Collections.singleton(rpc);
+ }
+
+ @Override
+ public void close() {
+ // no op
+ }
+ }
+}
--- /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.test.tool.customrpc;
+
+import java.io.File;
+import java.util.Optional;
+import org.opendaylight.controller.config.util.xml.DocumentedException;
+import org.opendaylight.controller.config.util.xml.XmlElement;
+import org.opendaylight.controller.config.util.xml.XmlUtil;
+import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
+import org.opendaylight.netconf.mapping.api.HandlingPriority;
+import org.opendaylight.netconf.mapping.api.NetconfOperation;
+import org.opendaylight.netconf.mapping.api.NetconfOperationChainedExecution;
+import org.w3c.dom.Document;
+
+/**
+ * {@link NetconfOperation} implementation. It can be configured to intercept rpcs with defined input
+ * and reply with defined output. If input isn't defined, rpc handling is delegated to the subsequent
+ * {@link NetconfOperation} which is able to handle it.
+ */
+class SettableRpc implements NetconfOperation {
+
+ private final RpcMapping mapping;
+
+ SettableRpc(final File rpcConfig) {
+ mapping = new RpcMapping(rpcConfig);
+ }
+
+ @Override
+ public HandlingPriority canHandle(final Document message) throws DocumentedException {
+ return HandlingPriority.HANDLE_WITH_DEFAULT_PRIORITY.increasePriority(1000);
+ }
+
+ @Override
+ public Document handle(final Document requestMessage, final NetconfOperationChainedExecution subsequentOperation)
+ throws DocumentedException {
+ final XmlElement requestElement = XmlElement.fromDomDocument(requestMessage);
+ final XmlElement rpcElement = requestElement.getOnlyChildElement();
+ final String msgId = requestElement.getAttribute(XmlNetconfConstants.MESSAGE_ID);
+ final Optional<Document> response = mapping.getResponse(rpcElement);
+ if (response.isPresent()) {
+ final Document document = response.get();
+ checkForError(document);
+ document.getDocumentElement().setAttribute(XmlNetconfConstants.MESSAGE_ID, msgId);
+ return document;
+ } else if (subsequentOperation.isExecutionTermination()) {
+ throw new DocumentedException("Mapping not found " + XmlUtil.toString(requestMessage),
+ DocumentedException.ErrorType.APPLICATION, DocumentedException.ErrorTag.OPERATION_NOT_SUPPORTED,
+ DocumentedException.ErrorSeverity.ERROR);
+ } else {
+ return subsequentOperation.execute(requestMessage);
+ }
+ }
+
+ private void checkForError(final Document document) throws DocumentedException {
+ final XmlElement rpcReply = XmlElement.fromDomDocument(document);
+ if (rpcReply.getOnlyChildElementOptionally("rpc-error").isPresent()) {
+ throw DocumentedException.fromXMLDocument(document);
+ }
+ }
+
+}
--- /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.test.tool.customrpc;
+
+import javax.xml.bind.annotation.XmlAnyElement;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+class XmlData {
+ @XmlAnyElement
+ private Element data;
+
+ Document getData() {
+ return data.getOwnerDocument();
+ }
+
+}
</dependencyManagement>
<dependencies>
+ <dependency>
+ <groupId>com.github.mifmif</groupId>
+ <artifactId>generex</artifactId>
+ <version>1.0.0</version>
+ </dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<groupId>org.opendaylight.yangtools</groupId>
<artifactId>yang-data-api</artifactId>
</dependency>
+ <dependency>
+ <groupId>dk.brics.automaton</groupId>
+ <artifactId>automaton</artifactId>
+ <version>1.11-8</version>
+ </dependency>
</dependencies>
<build>
<instructions>
<Bundle-Name>MD SAL Rest Api Doc Generator</Bundle-Name>
+ <Embed-Dependency>generex, automaton</Embed-Dependency>
<Import-Package>!org.apache.maven.plugin.logging,
!org.apache.maven.project,
!org.opendaylight.yangtools.yang2sources.spi,
</plugin>
</plugins>
</build>
-</project>
+</project>
\ No newline at end of file
return INSTANCE;
}
+ public void setDraft(final boolean newDraft) {
+ super.setDraft(newDraft);
+ }
+
public void setSchemaService(final SchemaService schemaService) {
this.schemaService = schemaService;
}
@Override
public synchronized Response getRootDoc(final UriInfo uriInfo) {
final ApiDocGenerator generator = ApiDocGenerator.getInstance();
+ if (isNew(uriInfo)) {
+ generator.setDraft(true);
+ } else {
+ generator.setDraft(false);
+ }
final ResourceList rootDoc = generator.getResourceListing(uriInfo);
return Response.ok(rootDoc).build();
@Override
public synchronized Response getDocByModule(final String module, final String revision, final UriInfo uriInfo) {
final ApiDocGenerator generator = ApiDocGenerator.getInstance();
-
+ if (isNew(uriInfo)) {
+ generator.setDraft(true);
+ } else {
+ generator.setDraft(false);
+ }
final ApiDeclaration doc = generator.getApiDeclaration(module, revision, uriInfo);
return Response.ok(doc).build();
}
@Override
public synchronized Response getMountRootDoc(final String instanceNum, final UriInfo uriInfo) {
- final ResourceList resourceList = MountPointSwagger.getInstance().getResourceList(uriInfo,
- Long.parseLong(instanceNum));
+ final ResourceList resourceList;
+ if (isNew(uriInfo)) {
+ resourceList = MountPointSwagger.getInstanceDraft17().getResourceList(uriInfo,
+ Long.parseLong(instanceNum));
+ } else {
+ resourceList = MountPointSwagger.getInstance().getResourceList(uriInfo,
+ Long.parseLong(instanceNum));
+ }
return Response.ok(resourceList).build();
}
@Override
public synchronized Response getMountDocByModule(final String instanceNum, final String module,
final String revision, final UriInfo uriInfo) {
- final ApiDeclaration api = MountPointSwagger.getInstance().getMountPointApi(uriInfo,
- Long.parseLong(instanceNum), module, revision);
+ final ApiDeclaration api;
+ if (isNew(uriInfo)) {
+ api = MountPointSwagger.getInstanceDraft17().getMountPointApi(uriInfo,
+ Long.parseLong(instanceNum), module, revision);
+ } else {
+ api = MountPointSwagger.getInstance().getMountPointApi(uriInfo,
+ Long.parseLong(instanceNum), module, revision);
+ }
return Response.ok(api).build();
}
+ private static boolean isNew(final UriInfo uriInfo) {
+ return uriInfo.getBaseUri().toString().contains("/17/");
+ }
}
protected static final String API_VERSION = "1.0.0";
protected static final String SWAGGER_VERSION = "1.2";
protected static final String RESTCONF_CONTEXT_ROOT = "restconf";
+ private static final String RESTCONF_DRAFT = "17";
static final String MODULE_NAME_SUFFIX = "_module";
protected static final DateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
// private Map<String, ApiDeclaration> MODULE_DOC_CACHE = new HashMap<>()
private final ObjectMapper mapper = new ObjectMapper();
+ private static boolean newDraft;
protected BaseYangSwaggerGenerator() {
mapper.registerModule(new JsonOrgModule());
* are added for this node.
*/
if (node.isConfiguration()) { // This node's config statement is true.
- resourcePath = getDataStorePath("/config/", context);
+ resourcePath = getDataStorePath("config", context);
/*
* When there are two or more top container or list nodes whose config statement is true in module,
*/
if (!hasAddRootPostLink) {
LOG.debug("Has added root post link for module {}", m.getName());
- addRootPostLink(m, (DataNodeContainer) node, pathParams, resourcePath, apis);
+ addRootPostLink(m, (DataNodeContainer) node, pathParams, resourcePath, "config", apis);
+
hasAddRootPostLink = true;
}
- addApis(node, apis, resourcePath, pathParams, schemaContext, true, m.getName());
+ addApis(node, apis, resourcePath, pathParams, schemaContext, true, m.getName(), "config");
}
-
pathParams = new ArrayList<>();
- resourcePath = getDataStorePath("/operational/", context);
- addApis(node, apis, resourcePath, pathParams, schemaContext, false, m.getName());
+ resourcePath = getDataStorePath("operational", context);
+
+ addApis(node, apis, resourcePath, pathParams, schemaContext, false, m.getName(), "operational");
}
}
final Set<RpcDefinition> rpcs = m.getRpcs();
for (final RpcDefinition rpcDefinition : rpcs) {
- final String resourcePath = getDataStorePath("/operations/", context);
+ final String resourcePath;
+ resourcePath = getDataStorePath("operations", context);
+
addRpcs(rpcDefinition, apis, resourcePath, schemaContext);
}
}
private void addRootPostLink(final Module module, final DataNodeContainer node, final List<Parameter> pathParams,
- final String resourcePath, final List<Api> apis) {
+ final String resourcePath, final String dataStore, final List<Api> apis) {
if (containsListOrContainer(module.getChildNodes())) {
final Api apiForRootPostUri = new Api();
- apiForRootPostUri.setPath(resourcePath);
+ apiForRootPostUri.setPath(resourcePath.concat(getContent(dataStore)));
apiForRootPostUri.setOperations(operationPost(module.getName() + MODULE_NAME_SUFFIX,
module.getDescription(), module, pathParams, true, ""));
apis.add(apiForRootPostUri);
}
protected String getDataStorePath(final String dataStore, final String context) {
- return dataStore + context;
+ if (newDraft) {
+ if ("config".contains(dataStore) || "operational".contains(dataStore)) {
+ return "/" + RESTCONF_DRAFT + "/data" + context;
+ } else {
+ return "/" + RESTCONF_DRAFT + "/operations" + context;
+ }
+ } else {
+ return "/" + dataStore + context;
+ }
}
private String generateCacheKey(final String module, final String revision) {
return module + "(" + revision + ")";
}
- private void addApis(final DataSchemaNode node, final List<Api> apis, final String parentPath, final List<Parameter> parentPathParams, final SchemaContext schemaContext,
- final boolean addConfigApi, final String parentName) {
-
+ private void addApis(final DataSchemaNode node, final List<Api> apis, final String parentPath, final List<Parameter> parentPathParams,
+ final SchemaContext schemaContext, final boolean addConfigApi, final String parentName, final String dataStore) {
final Api api = new Api();
final List<Parameter> pathParams = new ArrayList<>(parentPathParams);
- final String resourcePath = parentPath + createPath(node, pathParams, schemaContext) + "/";
+ final String resourcePath = parentPath + "/" + createPath(node, pathParams, schemaContext);
LOG.debug("Adding path: [{}]", resourcePath);
- api.setPath(resourcePath);
+ api.setPath(resourcePath.concat(getContent(dataStore)));
Iterable<DataSchemaNode> childSchemaNodes = Collections.<DataSchemaNode>emptySet();
if ((node instanceof ListSchemaNode) || (node instanceof ContainerSchemaNode)) {
// keep config and operation attributes separate.
if (childNode.isConfiguration() == addConfigApi) {
final String newParent = parentName + "/" + node.getQName().getLocalName();
- addApis(childNode, apis, resourcePath, pathParams, schemaContext, addConfigApi, newParent);
+ addApis(childNode, apis, resourcePath, pathParams, schemaContext, addConfigApi, newParent, dataStore);
}
}
}
}
+ protected static String getContent(final String dataStore) {
+ if (newDraft) {
+ if ("operational".contains(dataStore)) {
+ return "?content=nonconfig";
+ } else if ("config".contains(dataStore)) {
+ return "?content=config";
+ } else {
+ return "";
+ }
+ } else {
+ return "";
+ }
+ }
private boolean containsListOrContainer(final Iterable<DataSchemaNode> nodes) {
for (final DataSchemaNode child : nodes) {
if (child instanceof ListSchemaNode || child instanceof ContainerSchemaNode) {
if ((schemaNode instanceof ListSchemaNode)) {
final List<QName> listKeys = ((ListSchemaNode) schemaNode).getKeyDefinition();
+ StringBuilder keyBuilder = null;
+ if (newDraft) {
+ keyBuilder = new StringBuilder("=");
+ }
+
for (final QName listKey : listKeys) {
final DataSchemaNode dataChildByName = ((DataNodeContainer) schemaNode).getDataChildByName(listKey);
pathListParams.add(((LeafSchemaNode) dataChildByName));
-
- final String pathParamIdentifier = new StringBuilder("/{").append(listKey.getLocalName()).append("}")
- .toString();
+ final String pathParamIdentifier;
+ if (newDraft) {
+ pathParamIdentifier = keyBuilder.append("{").append(listKey.getLocalName()).append("}")
+ .toString();
+ } else {
+ pathParamIdentifier = "/{" + listKey.getLocalName() + "}";
+ }
path.append(pathParamIdentifier);
final Parameter pathParam = new Parameter();
pathParam.setParamType("path");
pathParams.add(pathParam);
+ if (newDraft) {
+ keyBuilder = new StringBuilder(",");
+ }
}
}
return path.toString();
protected void addRpcs(final RpcDefinition rpcDefn, final List<Api> apis, final String parentPath, final SchemaContext schemaContext) {
final Api rpc = new Api();
- final String resourcePath = parentPath + resolvePathArgumentsName(rpcDefn, schemaContext);
+ final String resourcePath = parentPath + "/" + resolvePathArgumentsName(rpcDefn, schemaContext);
rpc.setPath(resourcePath);
final Operation operationSpec = new Operation();
return sortedModules;
}
+ public void setDraft(final boolean draft) {
+ this.newDraft = draft;
+ }
}
import static org.opendaylight.netconf.sal.rest.doc.util.RestDocgenUtil.resolveNodesName;
-import com.google.common.base.Preconditions;
+import com.google.common.base.Optional;
+import com.mifmif.common.regex.Generex;
import java.io.IOException;
+import java.net.URI;
import java.util.ArrayList;
+import java.util.Date;
import java.util.List;
import java.util.Set;
+import java.util.regex.Pattern;
import javax.annotation.concurrent.NotThreadSafe;
import org.json.JSONArray;
import org.json.JSONException;
import org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder;
import org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.Post;
import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
import org.opendaylight.yangtools.yang.model.api.ConstraintDefinition;
import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath;
import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.opendaylight.yangtools.yang.model.api.SchemaNode;
import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.TypedSchemaNode;
import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition.Bit;
import org.opendaylight.yangtools.yang.model.api.type.BooleanTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.EmptyTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition.EnumPair;
import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.IntegerTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint;
+import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.UnsignedIntegerTypeDefinition;
+import org.opendaylight.yangtools.yang.model.util.RevisionAwareXPathImpl;
+import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger LOG = LoggerFactory.getLogger(ModelGenerator.class);
+ private static final Pattern STRIP_PATTERN = Pattern.compile("\\[[^\\[\\]]*\\]");
private static final String BASE_64 = "base64";
private static final String BINARY_ENCODING_KEY = "binaryEncoding";
private static final String MEDIA_KEY = "media";
- private static final String ONE_OF_KEY = "oneOf";
private static final String UNIQUE_ITEMS_KEY = "uniqueItems";
private static final String MAX_ITEMS = "maxItems";
private static final String MIN_ITEMS = "minItems";
private static final String OBJECT_TYPE = "object";
private static final String ARRAY_TYPE = "array";
private static final String ENUM = "enum";
- private static final String INTEGER = "integer";
- private static final String NUMBER = "number";
- private static final String BOOLEAN = "boolean";
- private static final String STRING = "string";
private static final String ID_KEY = "id";
private static final String SUB_TYPES_KEY = "subTypes";
+ private static final String UNIQUE_EMPTY_IDENTIFIER = "unique_empty_identifier";
private Module topLevelModule;
public ModelGenerator() {
}
- private static String jsonTypeFor(final TypeDefinition<?> type) {
- if (type instanceof BooleanTypeDefinition) {
- return BOOLEAN;
- } else if (type instanceof DecimalTypeDefinition) {
- return NUMBER;
- } else if (type instanceof EnumTypeDefinition) {
- return ENUM;
- } else if (type instanceof IntegerTypeDefinition) {
- return INTEGER;
- } else if (type instanceof UnsignedIntegerTypeDefinition) {
- return INTEGER;
- } else if (type instanceof StringTypeDefinition) {
- return STRING;
- }
-
- // TODO: Binary type
- return null;
- }
-
+ /**
+ * Creates Json models from provided module according to swagger spec
+ *
+ * @param module - Yang module to be converted
+ * @param schemaContext - SchemaContext of all Yang files used by Api Doc
+ * @return JSONObject containing data used for creating examples and models in Api Doc
+ * @throws IOException
+ * @throws JSONException
+ */
public JSONObject convertToJsonSchema(final Module module, final SchemaContext schemaContext) throws IOException, JSONException {
final JSONObject models = new JSONObject();
+ models.put(UNIQUE_EMPTY_IDENTIFIER, new JSONObject());
topLevelModule = module;
- processModules(module, models);
+ processModules(module, models, schemaContext);
processContainersAndLists(module, models, schemaContext);
processRPCs(module, models, schemaContext);
processIdentities(module, models);
return models;
}
- private void processModules(final Module module, final JSONObject models) throws JSONException {
+ private void processModules(final Module module, final JSONObject models, final SchemaContext schemaContext) throws JSONException {
createConcreteModelForPost(models, module.getName() + BaseYangSwaggerGenerator.MODULE_NAME_SUFFIX,
- createPropertiesForPost(module, module.getName()));
+ createPropertiesForPost(module, schemaContext, module.getName()));
}
private void processContainersAndLists(final Module module, final JSONObject models, final SchemaContext schemaContext)
throws IOException, JSONException {
-
final String moduleName = module.getName();
for (final DataSchemaNode childNode : module.getChildNodes()) {
final JSONObject childSchema = getSchemaTemplate();
childSchema.put(TYPE_KEY, OBJECT_TYPE);
childSchema.put(PROPERTIES_KEY, properties);
- childSchema.put("id", filename);
+ childSchema.put(ID_KEY, filename);
models.put(filename, childSchema);
processTopData(filename, models, input);
final JSONObject childSchema = getSchemaTemplate();
childSchema.put(TYPE_KEY, OBJECT_TYPE);
childSchema.put(PROPERTIES_KEY, properties);
- childSchema.put("id", filename);
+ childSchema.put(ID_KEY, filename);
models.put(filename, childSchema);
processTopData(filename, models, output);
}
}
- private static JSONObject processTopData(final String filename, final JSONObject models, final SchemaNode schemaNode) {
+ private JSONObject processTopData(final String filename, final JSONObject models, final SchemaNode schemaNode) {
final JSONObject items = new JSONObject();
items.put(REF_KEY, filename);
dataNodeProperties.putOpt(DESCRIPTION_KEY, schemaNode.getDescription());
final JSONObject properties = new JSONObject();
- properties.put(schemaNode.getQName().getLocalName(), dataNodeProperties);
+ properties.put(topLevelModule.getName() + ":" + schemaNode.getQName().getLocalName(), dataNodeProperties);
final JSONObject finalChildSchema = getSchemaTemplate();
finalChildSchema.put(TYPE_KEY, OBJECT_TYPE);
finalChildSchema.put(PROPERTIES_KEY, properties);
/**
* Processes the 'identity' statement in a yang model and maps it to a 'model' in the Swagger JSON spec.
*
- * @param module
- * The module from which the identity stmt will be processed
- * @param models
- * The JSONObject in which the parsed identity will be put as a 'model' obj
+ * @param module The module from which the identity stmt will be processed
+ * @param models The JSONObject in which the parsed identity will be put as a 'model' obj
*/
private static void processIdentities(final Module module, final JSONObject models) throws JSONException {
subTypes.put(derivedId.getQName().getLocalName());
}
identityObj.put(SUB_TYPES_KEY, subTypes);
+
}
} else {
/**
private JSONObject processDataNodeContainer(final DataNodeContainer dataNode, final String parentName, final JSONObject models,
final boolean isConfig, final SchemaContext schemaContext) throws JSONException, IOException {
if (dataNode instanceof ListSchemaNode || dataNode instanceof ContainerSchemaNode) {
- Preconditions.checkArgument(dataNode instanceof SchemaNode, "Data node should be also schema node");
final Iterable<DataSchemaNode> containerChildren = dataNode.getChildNodes();
final String localName = ((SchemaNode) dataNode).getQName().getLocalName();
final JSONObject properties = processChildren(containerChildren, parentName + "/" + localName, models, isConfig, schemaContext);
childSchema.put(TYPE_KEY, OBJECT_TYPE);
childSchema.put(PROPERTIES_KEY, properties);
- childSchema.put("id", nodeName);
+ childSchema.put(ID_KEY, nodeName);
models.put(nodeName, childSchema);
if (isConfig) {
createConcreteModelForPost(models, localName,
- createPropertiesForPost(dataNode, parentName + "/" + localName));
+ createPropertiesForPost(dataNode, schemaContext, parentName + "/" + localName));
}
return processTopData(nodeName, models, (SchemaNode) dataNode);
}
private static void createConcreteModelForPost(final JSONObject models, final String localName,
- final JSONObject properties) throws JSONException {
+ final JSONObject properties) throws JSONException {
final String nodePostName = OperationBuilder.CONFIG + localName + Post.METHOD_NAME;
final JSONObject postSchema = getSchemaTemplate();
postSchema.put(TYPE_KEY, OBJECT_TYPE);
- postSchema.put("id", nodePostName);
+ postSchema.put(ID_KEY, nodePostName);
postSchema.put(PROPERTIES_KEY, properties);
models.put(nodePostName, postSchema);
}
- private JSONObject createPropertiesForPost(final DataNodeContainer dataNodeContainer, final String parentName)
+ private JSONObject createPropertiesForPost(final DataNodeContainer dataNodeContainer, final SchemaContext schemaContext, final String parentName)
throws JSONException {
final JSONObject properties = new JSONObject();
for (final DataSchemaNode childNode : dataNodeContainer.getChildNodes()) {
property.put(ITEMS_KEY, items);
properties.put(childNode.getQName().getLocalName(), property);
} else if (childNode instanceof LeafSchemaNode) {
- final JSONObject property = processLeafNode((LeafSchemaNode) childNode);
+ final JSONObject property = processLeafNode((LeafSchemaNode) childNode, schemaContext);
properties.put(childNode.getQName().getLocalName(), property);
}
}
return properties;
}
- private JSONObject processChildren(final Iterable<DataSchemaNode> nodes, final String moduleName,
- final JSONObject models, final SchemaContext schemaContext) throws JSONException, IOException {
- return processChildren(nodes, moduleName, models, true, schemaContext);
- }
-
/**
* Processes the nodes.
*/
final String name = resolveNodesName(node, topLevelModule, schemaContext);
final JSONObject property;
if (node instanceof LeafSchemaNode) {
- property = processLeafNode((LeafSchemaNode) node);
+ property = processLeafNode((LeafSchemaNode) node, schemaContext);
} else if (node instanceof ListSchemaNode) {
property = processDataNodeContainer((ListSchemaNode) node, parentName, models, isConfig,
schemaContext);
} else if (node instanceof LeafListSchemaNode) {
- property = processLeafListNode((LeafListSchemaNode) node);
+ property = processLeafListNode((LeafListSchemaNode) node, schemaContext);
} else if (node instanceof ChoiceSchemaNode) {
- property = processChoiceNode((ChoiceSchemaNode) node, parentName, models, schemaContext);
+ if (((ChoiceSchemaNode) node).getCases().iterator().hasNext()) {
+ processChoiceNode(((ChoiceSchemaNode) node).getCases().iterator().next().getChildNodes(),
+ parentName, models, schemaContext, isConfig, properties);
+ }
+ continue;
} else if (node instanceof AnyXmlSchemaNode) {
property = processAnyXMLNode((AnyXmlSchemaNode) node);
throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
}
property.putOpt(DESCRIPTION_KEY, node.getDescription());
- properties.put(name, property);
+ properties.put(topLevelModule.getName() + ":" + name, property);
}
}
return properties;
}
- private JSONObject processLeafListNode(final LeafListSchemaNode listNode) throws JSONException {
+ private JSONObject processLeafListNode(final LeafListSchemaNode listNode, final SchemaContext schemaContext) throws JSONException {
final JSONObject props = new JSONObject();
props.put(TYPE_KEY, ARRAY_TYPE);
final JSONObject itemsVal = new JSONObject();
- processTypeDef(listNode.getType(), itemsVal);
+ final ConstraintDefinition constraints = listNode.getConstraints();
+ final Optional<Integer> maxOptional = Optional.fromNullable(constraints.getMaxElements());
+ if (maxOptional.or(2) >= 2) {
+ processTypeDef(listNode.getType(), listNode, itemsVal, schemaContext);
+ processTypeDef(listNode.getType(), listNode, itemsVal, schemaContext);
+ } else {
+ processTypeDef(listNode.getType(), listNode, itemsVal, schemaContext);
+ }
props.put(ITEMS_KEY, itemsVal);
- final ConstraintDefinition constraints = listNode.getConstraints();
+
processConstraints(constraints, props);
return props;
}
- private JSONObject processChoiceNode(final ChoiceSchemaNode choiceNode, final String parentName, final JSONObject models,
- final SchemaContext schemaContext) throws JSONException, IOException {
+ private void processChoiceNode(final Iterable<DataSchemaNode> nodes, final String moduleName, final JSONObject models,
+ final SchemaContext schemaContext, final boolean isConfig, final JSONObject properties)
+ throws JSONException, IOException {
+ for (final DataSchemaNode node : nodes) {
+ final String name = resolveNodesName(node, topLevelModule, schemaContext);
+ final JSONObject property;
- final Set<ChoiceCaseNode> cases = choiceNode.getCases();
+ if (node instanceof LeafSchemaNode) {
+ property = processLeafNode((LeafSchemaNode) node, schemaContext);
- final JSONArray choiceProps = new JSONArray();
- for (final ChoiceCaseNode choiceCase : cases) {
- final String choiceName = choiceCase.getQName().getLocalName();
- final JSONObject choiceProp = processChildren(choiceCase.getChildNodes(), parentName, models, schemaContext);
- final JSONObject choiceObj = new JSONObject();
- choiceObj.put(choiceName, choiceProp);
- choiceObj.put(TYPE_KEY, OBJECT_TYPE);
- choiceProps.put(choiceObj);
- }
+ } else if (node instanceof ListSchemaNode) {
+ property = processDataNodeContainer((ListSchemaNode) node, moduleName, models, isConfig,
+ schemaContext);
+
+ } else if (node instanceof LeafListSchemaNode) {
+ property = processLeafListNode((LeafListSchemaNode) node, schemaContext);
- final JSONObject oneOfProps = new JSONObject();
- oneOfProps.put(ONE_OF_KEY, choiceProps);
- oneOfProps.put(TYPE_KEY, OBJECT_TYPE);
+ } else if (node instanceof ChoiceSchemaNode) {
+ if (((ChoiceSchemaNode) node).getCases().iterator().hasNext())
+ processChoiceNode(((ChoiceSchemaNode) node).getCases().iterator().next().getChildNodes(),
+ moduleName, models, schemaContext, isConfig, properties);
+ continue;
- return oneOfProps;
+ } else if (node instanceof AnyXmlSchemaNode) {
+ property = processAnyXMLNode((AnyXmlSchemaNode) node);
+
+ } else if (node instanceof ContainerSchemaNode) {
+ property = processDataNodeContainer((ContainerSchemaNode) node, moduleName, models, isConfig,
+ schemaContext);
+
+ } else {
+ throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
+ }
+
+ property.putOpt(DESCRIPTION_KEY, node.getDescription());
+ properties.put(name, property);
+ }
}
private static void processConstraints(final ConstraintDefinition constraints, final JSONObject props) throws JSONException {
}
}
- private JSONObject processLeafNode(final LeafSchemaNode leafNode) throws JSONException {
+ private JSONObject processLeafNode(final LeafSchemaNode leafNode, final SchemaContext schemaContext) throws JSONException {
final JSONObject property = new JSONObject();
final String leafDescription = leafNode.getDescription();
property.put(DESCRIPTION_KEY, leafDescription);
-
processConstraints(leafNode.getConstraints(), property);
- processTypeDef(leafNode.getType(), property);
+ processTypeDef(leafNode.getType(), leafNode, property, schemaContext);
return property;
}
property.put(DESCRIPTION_KEY, leafDescription);
processConstraints(leafNode.getConstraints(), property);
+ final String localName = leafNode.getQName().getLocalName();
+ property.put(TYPE_KEY, "example of anyxml " + localName);
return property;
}
- private void processTypeDef(final TypeDefinition<?> leafTypeDef, final JSONObject property) throws JSONException {
- if (leafTypeDef instanceof BinaryTypeDefinition) {
- processBinaryType((BinaryTypeDefinition) leafTypeDef, property);
- } else if (leafTypeDef instanceof BitsTypeDefinition) {
- processBitsType((BitsTypeDefinition) leafTypeDef, property);
- } else if (leafTypeDef instanceof EnumTypeDefinition) {
- processEnumType((EnumTypeDefinition) leafTypeDef, property);
- } else if (leafTypeDef instanceof IdentityrefTypeDefinition) {
- property.putOpt(TYPE_KEY,
- ((IdentityrefTypeDefinition) leafTypeDef).getIdentity().getQName().getLocalName());
- } else if (leafTypeDef instanceof StringTypeDefinition) {
- processStringType((StringTypeDefinition) leafTypeDef, property);
- } else if (leafTypeDef instanceof UnionTypeDefinition) {
- processUnionType((UnionTypeDefinition) leafTypeDef, property);
- } else {
- String jsonType = jsonTypeFor(leafTypeDef);
- if (jsonType == null) {
- jsonType = "object";
+ private String processTypeDef(final TypeDefinition<?> leafTypeDef, final DataSchemaNode node,
+ final JSONObject property, final SchemaContext schemaContext) throws JSONException {
+ final String jsonType;
+ if (leafTypeDef.getDefaultValue() == null) {
+ if (leafTypeDef instanceof BinaryTypeDefinition) {
+ jsonType = processBinaryType(property);
+
+ } else if (leafTypeDef instanceof BitsTypeDefinition) {
+ jsonType = processBitsType((BitsTypeDefinition) leafTypeDef, property);
+
+ } else if (leafTypeDef instanceof EnumTypeDefinition) {
+ jsonType = processEnumType((EnumTypeDefinition) leafTypeDef, property);
+
+ } else if (leafTypeDef instanceof IdentityrefTypeDefinition) {
+ final String name = topLevelModule.getName();
+ jsonType = name + ":" + ((IdentityrefTypeDefinition) leafTypeDef).getIdentity().getQName().getLocalName();
+
+ } else if (leafTypeDef instanceof StringTypeDefinition) {
+ jsonType = processStringType(leafTypeDef, property, node.getQName().getLocalName());
+
+ } else if (leafTypeDef instanceof UnionTypeDefinition) {
+ jsonType = processUnionType((UnionTypeDefinition) leafTypeDef, property, schemaContext, node);
+
+ } else if (leafTypeDef instanceof EmptyTypeDefinition) {
+ jsonType = UNIQUE_EMPTY_IDENTIFIER;
+
+ } else if (leafTypeDef instanceof LeafrefTypeDefinition) {
+ return processLeafRef(node, property, schemaContext, leafTypeDef);
+
+ } else if (leafTypeDef instanceof BooleanTypeDefinition) {
+ jsonType = "true";
+
+ } else if (leafTypeDef instanceof DecimalTypeDefinition) {
+ jsonType = String.valueOf(((DecimalTypeDefinition) leafTypeDef).getRangeConstraints()
+ .iterator().next().getMin());
+
+ } else if (leafTypeDef instanceof IntegerTypeDefinition) {
+ jsonType = String.valueOf(((IntegerTypeDefinition) leafTypeDef).getRangeConstraints()
+ .iterator().next().getMin());
+
+ } else if (leafTypeDef instanceof UnsignedIntegerTypeDefinition) {
+ jsonType = String.valueOf(((UnsignedIntegerTypeDefinition) leafTypeDef).getRangeConstraints()
+ .iterator().next().getMin());
+
+ } else {
+ jsonType = OBJECT_TYPE;
+
}
- property.putOpt(TYPE_KEY, jsonType);
+ } else {
+ jsonType = String.valueOf(leafTypeDef.getDefaultValue());
}
+ property.putOpt(TYPE_KEY, jsonType);
+ return jsonType;
}
- private static void processBinaryType(final BinaryTypeDefinition binaryType, final JSONObject property) throws JSONException {
- property.put(TYPE_KEY, STRING);
+ private String processLeafRef(final DataSchemaNode node, final JSONObject property, final SchemaContext schemaContext,
+ final TypeDefinition<?> leafTypeDef) {
+ RevisionAwareXPath xPath = ((LeafrefTypeDefinition) leafTypeDef).getPathStatement();
+ final URI namespace = leafTypeDef.getQName().getNamespace();
+ final Date revision = leafTypeDef.getQName().getRevision();
+ final Module module = schemaContext.findModuleByNamespaceAndRevision(namespace, revision);
+ final SchemaNode schemaNode;
+
+ final String xPathString = STRIP_PATTERN.matcher(xPath.toString()).replaceAll("");
+ xPath = new RevisionAwareXPathImpl(xPathString, xPath.isAbsolute());
+
+ if (xPath.isAbsolute()) {
+ schemaNode = SchemaContextUtil.findDataSchemaNode(schemaContext, module, xPath);
+ } else {
+ schemaNode = SchemaContextUtil.findDataSchemaNodeForRelativeXPath(schemaContext, module, node, xPath);
+ }
+
+ return processTypeDef(((TypedSchemaNode) schemaNode).getType(), (DataSchemaNode) schemaNode, property, schemaContext);
+ }
+
+ private static String processBinaryType(final JSONObject property) throws JSONException {
final JSONObject media = new JSONObject();
media.put(BINARY_ENCODING_KEY, BASE_64);
property.put(MEDIA_KEY, media);
+ return "bin1 bin2";
}
- private static void processEnumType(final EnumTypeDefinition enumLeafType, final JSONObject property) throws JSONException {
+ private static String processEnumType(final EnumTypeDefinition enumLeafType, final JSONObject property) throws JSONException {
final List<EnumPair> enumPairs = enumLeafType.getValues();
final List<String> enumNames = new ArrayList<>();
for (final EnumPair enumPair : enumPairs) {
enumNames.add(enumPair.getName());
}
+
property.putOpt(ENUM, new JSONArray(enumNames));
+ return enumLeafType.getValues().iterator().next().getName();
}
- private static void processBitsType(final BitsTypeDefinition bitsType, final JSONObject property) throws JSONException {
- property.put(TYPE_KEY, ARRAY_TYPE);
+ private static String processBitsType(final BitsTypeDefinition bitsType, final JSONObject property) throws JSONException {
property.put(MIN_ITEMS, 0);
property.put(UNIQUE_ITEMS_KEY, true);
- final JSONArray enumValues = new JSONArray();
-
+ final List<String> enumNames = new ArrayList<>();
final List<Bit> bits = bitsType.getBits();
for (final Bit bit : bits) {
- enumValues.put(bit.getName());
+ enumNames.add(bit.getName());
}
- final JSONObject itemsValue = new JSONObject();
- itemsValue.put(ENUM, enumValues);
- property.put(ITEMS_KEY, itemsValue);
+ property.put(ENUM, new JSONArray(enumNames));
+
+ return enumNames.iterator().next() + " " + enumNames.get(enumNames.size() - 1);
}
- private static void processStringType(final StringTypeDefinition stringType, final JSONObject property) throws JSONException {
- StringTypeDefinition type = stringType;
- List<LengthConstraint> lengthConstraints = stringType.getLengthConstraints();
+ private static String processStringType(final TypeDefinition<?> stringType, final JSONObject property, final String nodeName)
+ throws JSONException {
+ StringTypeDefinition type = (StringTypeDefinition) stringType;
+ List<LengthConstraint> lengthConstraints = ((StringTypeDefinition) stringType).getLengthConstraints();
while (lengthConstraints.isEmpty() && type.getBaseType() != null) {
type = type.getBaseType();
lengthConstraints = type.getLengthConstraints();
property.putOpt(MIN_LENGTH_KEY, min);
property.putOpt(MAX_LENGTH_KEY, max);
}
-
- property.put(TYPE_KEY, STRING);
+ if (type.getPatternConstraints().iterator().hasNext()) {
+ final PatternConstraint pattern = type.getPatternConstraints().iterator().next();
+ String regex = pattern.getRegularExpression();
+ regex = regex.substring(1, regex.length() - 1);
+ final Generex generex = new Generex(regex);
+ return generex.random();
+ } else {
+ return "Some " + nodeName;
+ }
}
- private static void processUnionType(final UnionTypeDefinition unionType, final JSONObject property) throws JSONException {
- final StringBuilder type = new StringBuilder();
+ private String processUnionType(final UnionTypeDefinition unionType, final JSONObject property,
+ final SchemaContext schemaContext, final DataSchemaNode node)
+ throws JSONException {
+ final List<String> unionNames = new ArrayList<>();
for (final TypeDefinition<?> typeDef : unionType.getTypes()) {
- if (type.length() > 0) {
- type.append(" or ");
- }
- type.append(jsonTypeFor(typeDef));
+ unionNames.add(processTypeDef(typeDef, node, property, schemaContext));
}
-
- property.put(TYPE_KEY, type);
+ property.put(ENUM, new JSONArray(unionNames));
+ return unionNames.iterator().next();
}
/**
return schemaJSON;
}
-}
+}
\ No newline at end of file
private static final String DATASTORES_REVISION = "-";
private static final String DATASTORES_LABEL = "Datastores";
+ private static final String RESTCONF_DRAFT = "17";
private DOMMountPointService mountService;
private final Map<YangInstanceIdentifier, Long> instanceIdToLongId = new TreeMap<>(
private static final AtomicReference<MountPointSwagger> selfRef = new AtomicReference<>();
private SchemaService globalSchema;
-
+ private static boolean newDraft;
public Map<String, Long> getInstanceIdentifiers() {
final Map<String, Long> urlToId = new HashMap<>();
synchronized (lock) {
private String generateUrlPrefixFromInstanceID(final YangInstanceIdentifier key, final String moduleName) {
final StringBuilder builder = new StringBuilder();
+ builder.append("/");
if (moduleName != null) {
- builder.append(moduleName);
- builder.append(':');
+ builder.append(moduleName)
+ .append(':');
}
for (final PathArgument arg : key.getPathArguments()) {
final String name = arg.getNodeType().getLocalName();
if (arg instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) {
final NodeIdentifierWithPredicates nodeId = (NodeIdentifierWithPredicates) arg;
for (final Entry<QName, Object> entry : nodeId.getKeyValues().entrySet()) {
- builder.append(entry.getValue()).append('/');
+ if (newDraft) {
+ builder.deleteCharAt(builder.length() - 1)
+ .append("=")
+ .append(entry.getValue())
+ .append('/');
+ } else {
+ builder.append(entry.getValue())
+ .append('/');
+ }
}
} else {
- builder.append(name);
- builder.append('/');
+ builder.append(name)
+ .append('/');
}
}
return builder.toString();
private String getYangMountUrl(final YangInstanceIdentifier key) {
final String modName = findModuleName(key, globalSchema.getGlobalContext());
- return generateUrlPrefixFromInstanceID(key, modName) + "yang-ext:mount/";
+ return generateUrlPrefixFromInstanceID(key, modName) + "yang-ext:mount";
}
public ResourceList getResourceList(final UriInfo uriInfo, final Long id) {
getConfig.setNotes(note);
final Api api = new Api();
- api.setPath(getDataStorePath("/" + datastore + "/", context));
+ api.setPath(getDataStorePath(datastore, context).concat(getContent(datastore)));
api.setOperations(Collections.singletonList(getConfig));
return api;
selfRef.compareAndSet(null, new MountPointSwagger());
swagger = selfRef.get();
}
+ newDraft = false;
return swagger;
}
+ public static MountPointSwagger getInstanceDraft17() {
+ MountPointSwagger swagger = selfRef.get();
+ if (swagger == null) {
+ selfRef.compareAndSet(null, new MountPointSwagger());
+ swagger = selfRef.get();
+ }
+ newDraft = true;
+ return swagger;
+ }
}
--- /dev/null
+<!DOCTYPE html>\r
+<!--\r
+ ~ Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.\r
+ ~\r
+ ~ This program and the accompanying materials are made available under the\r
+ ~ terms of the Eclipse Public License v1.0 which accompanies this distribution,\r
+ ~ and is available at http://www.eclipse.org/legal/epl-v10.html\r
+ -->\r
+\r
+<html>\r
+<head>\r
+ <title>RestConf Documentation</title>\r
+ <link href='//fonts.googleapis.com/css?family=Droid+Sans:400,700'\r
+ rel='stylesheet' type='text/css'/>\r
+ <link href='../../explorer/css/highlight.default.css' media='screen' rel='stylesheet'\r
+ type='text/css'/>\r
+ <link href='../../explorer/css/screen.css' media='screen' rel='stylesheet'\r
+ type='text/css'/>\r
+ <link rel="stylesheet" type="text/css" href="../../explorer/css/opendaylight.css">\r
+ <link rel="stylesheet" type="text/css"\r
+ href="../../explorer/css/ui-lightness/jquery-ui-1.10.4.custom.min.css">\r
+ <script type="text/javascript" src="../../explorer/lib/shred.bundle.js"></script>\r
+ <script src='../../explorer/lib/jquery-1.8.0.min.js' type='text/javascript'></script>\r
+ <script src='../../explorer/lib/jquery-ui-1.11.0.min.js' type="text/javascript"></script>\r
+ <script src='../../explorer/lib/jquery.slideto.min.js' type='text/javascript'></script>\r
+ <script src='../../explorer/lib/jquery.wiggle.min.js' type='text/javascript'></script>\r
+ <script src='../../explorer/lib/jquery.ba-bbq.min.js' type='text/javascript'></script>\r
+ <script src='../../explorer/lib/handlebars-1.0.0.js' type='text/javascript'></script>\r
+ <script src='../../explorer/lib/underscore-min.js' type='text/javascript'></script>\r
+ <script src='../../explorer/lib/backbone-min.js' type='text/javascript'></script>\r
+ <script src='../../explorer/lib/swagger.js' type='text/javascript'></script>\r
+ <script src='../../explorer/swagger-ui.js' type='text/javascript'></script>\r
+ <script src='lib/odl/list_mounts.js' type='text/javascript'></script>\r
+ <script src='../../explorer/lib/highlight.7.3.pack.js' type='text/javascript'></script>\r
+ <script src='../../explorer/lib/odl/swagger.js' type='text/javascript'></script>\r
+\r
+ <script type="text/javascript">\r
+ \r
+ //reloads the swagger UI documentation for the specified mount.\r
+ var loadMount = function(mountIndex, mountPath) {\r
+ $("#message").empty();\r
+ $("#message").append( "<p>Loading...</p>" );\r
+ loadSwagger("/apidoc/17/apis/mounts/" + mountIndex,\r
+ "swagger-ui-container");\r
+ $("#message").empty();\r
+ $("#message").append( "<h2><b>Showing mount points for " + mountPath + "</b></h2>");\r
+ }\r
+\r
+ //clears the swagger UI and adds text prompting use to select a mount point.\r
+ var selectAMount = function(string) {\r
+ $("#swagger-ui-container").empty();\r
+ $("#message").empty();\r
+ $("#message").append("<p>Select a mount point.</p>");\r
+ }\r
+ \r
+ //loads the root swagger documenation (which comes from RestConf)\r
+ var loadRootSwagger = function() {\r
+ $("#message").empty();\r
+ loadSwagger("/apidoc/17/apis", "swagger-ui-container");\r
+ }\r
+\r
+ //main method to initialize the mount list / swagger docs / tabs on page load\r
+ $(function() {\r
+ $("#tabs").tabs();\r
+\r
+ loadMountList($("#mountlist"));\r
+\r
+ loadRootSwagger();\r
+ });\r
+ </script>\r
+</head>\r
+\r
+<body>\r
+<div>\r
+ <!-- style="background-color: #FCA000;" -->\r
+ <div class="swagger-ui-wrap ui-tabs">\r
+ <table>\r
+ <tr>\r
+ <td><img src="../../explorer/images/logo_small.png"/></td>\r
+ <td><h1 width="100%">OpenDaylight RestConf API\r
+ Documentation</h1></td>\r
+ </tr>\r
+ </table>\r
+ </div>\r
+</div>\r
+\r
+<div class="navbar-inner">\r
+ <div class="brand"></div>\r
+</div>\r
+\r
+<!-- defines the div shells which represent the jquery tabs -->\r
+<div id="tabs" class="swagger-ui-wrap">\r
+ <ul>\r
+ <li><a href="#Controller" onclick="loadRootSwagger()">Controller\r
+ Resources</a></li>\r
+ <li><a href="#Mounts" onclick="selectAMount()">Mounted\r
+ Resources</a></li>\r
+ </ul>\r
+\r
+ <div id="Controller">\r
+ <div>\r
+ <h3>Below are the list of APIs supported by the Controller.</h3>\r
+ </div>\r
+ </div>\r
+ <div id="Mounts">\r
+ <div>\r
+ <h3>Mount Points - Select an API below for details on available\r
+ queries.</h3>\r
+ </div>\r
+ <div id="mountlist"></div>\r
+ </div>\r
+</div>\r
+\r
+<div class="swagger-ui-wrap">\r
+ <hr/>\r
+</div>\r
+\r
+<!-- messages -->\r
+<div id="message" class="swagger-ui-wrap"></div>\r
+\r
+<!-- the swagger is always loaded in this div -->\r
+<div id="swagger-ui-container" class="swagger-ui-wrap"></div>\r
+\r
+<div id="message-bar" class="swagger-ui-wrap"> </div>\r
+</body>\r
+\r
+</html>\r
--- /dev/null
+//constructs a table of mount points via a json response
+//Loads the table into the given dom.
+var loadMountList = function( dom ) {
+ dom.empty();
+ dom.append( "<p>Loading. Please wait...</p>" );
+ $.ajax( {
+ url: "/apidoc/17/apis/mounts",
+ datatype: 'jsonp',
+ success: function( strData ){
+ var myData = strData;
+ var list = $( "<table></table>" );
+ for( var key in myData )
+ {
+ list.append( "<tr><td><a href=\"#\" onclick=\"loadMount(" +
+ myData[key].id + ", '" + myData[key].instance + "')\">" +
+ myData[key].instance + "</a></td></tr>");
+ }
+ dom.empty();
+ dom.append( list );
+ }
+ } );
+}
\ No newline at end of file
--- /dev/null
+<!DOCTYPE html>
+<!--
+ ~ 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
+ -->
+
+<html>
+<head>
+ <title>RestConf Documentation</title>
+ <link href='../../../eplorer/static/fonts.css'
+ rel='stylesheet' type='text/css' /> <!--original location: //fonts.googleapis.com/css?family=Droid+Sans:400,700 -->
+ <link href='../../../explorer/css/highlight.default.css' media='screen' rel='stylesheet'
+ type='text/css' />
+ <link href='../../../explorer/css/screen.css' media='screen' rel='stylesheet'
+ type='text/css' />
+ <link rel="stylesheet" type="text/css" href="../../../eplorer/static/opendaylight.css">
+ <link rel="stylesheet" type="text/css"
+ href="../../../explorer/css/ui-lightness/jquery-ui-1.10.4.custom.min.css">
+ <script type="text/javascript" src="../../../explorer/lib/shred.bundle.js"></script>
+ <script src='../../../explorer/lib/jquery-1.8.0.min.js' type='text/javascript'></script>
+ <script src='../../../explorer/lib/jquery-ui-1.11.0.min.js' type="text/javascript"></script>
+ <script src='../../../explorer/lib/jquery.slideto.min.js' type='text/javascript'></script>
+ <script src='../../../explorer/lib/jquery.wiggle.min.js' type='text/javascript'></script>
+ <script src='../../../explorer/lib/jquery.ba-bbq.min.js' type='text/javascript'></script>
+ <script src='../../../explorer/lib/handlebars-1.0.0.js' type='text/javascript'></script>
+ <script src='../../../explorer/lib/underscore-min.js' type='text/javascript'></script>
+ <script src='../../../explorer/lib/backbone-min.js' type='text/javascript'></script>
+ <script src='../../explorer/swagger.js' type='text/javascript'></script>
+ <script src='../../explorer/swagger-ui.js' type='text/javascript'></script>
+ <script src='../lib/odl/list_mounts.js' type='text/javascript'></script>
+ <script src='../../../explorer/lib/highlight.7.3.pack.js' type='text/javascript'></script>
+ <script src='../../../explorer/lib/odl/swagger.js' type='text/javascript'></script>
+ <script src='resources.js' type='text/javascript'></script>
+
+ <script type="text/javascript">
+
+ //reloads the swagger UI documentation for the specified mount.
+ var loadMount = function(mountIndex, mountPath) {
+ $("#message").empty();
+ $("#message").append( "<p>Loading...</p>" );
+ loadSwagger("/apidoc/apis/mounts/" + mountIndex,
+ "swagger-ui-container");
+ $("#message").empty();
+ $("#message").append( "<h2><b>Showing mount points for " + mountPath + "</b></h2>");
+ }
+
+ //clears the swagger UI and adds text prompting use to select a mount point.
+ var selectAMount = function(string) {
+ $("#swagger-ui-container").empty();
+ $("#message").empty();
+ $("#message").append("<p>Select a mount point.</p>");
+ }
+
+ //loads the root swagger documenation (which comes from RestConf)
+ var loadRootSwagger = function() {
+ $("#message").empty();
+ loadSwagger("/apidoc/apis", "swagger-ui-container");
+ }
+
+ //main method to initialize the mount list / swagger docs / tabs on page load
+ $(function() {
+ $("#tabs").tabs();
+
+ loadMountList($("#mountlist"));
+
+ loadRootSwagger();
+ });
+ </script>
+</head>
+
+<body>
+<div>
+ <!-- style="background-color: #FCA000;" -->
+ <div class="swagger-ui-wrap ui-tabs">
+ <table>
+ <tr>
+ <td><img src="../../../explorer/images/logo_small.png"/></td>
+ <td><h1 width="100%">OpenDaylight RestConf API
+ Documentation</h1></td>
+ </tr>
+ </table>
+ </div>
+</div>
+
+<div class="navbar-inner">
+ <div class="brand"></div>
+</div>
+
+<!-- defines the div shells which represent the jquery tabs -->
+<div id="tabs" class="swagger-ui-wrap">
+ <ul>
+ <li><a href="#Controller" onclick="loadRootSwagger()">Controller
+ Resources</a></li>
+ </ul>
+
+ <div id="Controller">
+ <div>
+ <h3>Below are the list of APIs supported by the Controller.</h3>
+ </div>
+ </div>
+</div>
+
+<div class="swagger-ui-wrap"><hr/></div>
+
+<!-- messages -->
+<div id="message" class="swagger-ui-wrap"></div>
+
+<!-- the swagger is always loaded in this div -->
+<div id="swagger-ui-container" class="swagger-ui-wrap"></div>
+
+<div id="message-bar" class="swagger-ui-wrap"> </div>
+</body>
+
+</html>
This Swagger documentation can be accessed in two ways:
I. Running server
Open a browser and go to http://<host>:8181/apidoc/explorer/index.html
+For new restconf draft go to http://<host>:8181/apidoc/17/explorer/index.html
II. Static documentation generation
By adding a reference to the StaticDocGenerator class in any pom.xml,
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
- version="3.0">
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ version="3.0">
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</servlet>
<context-param>
- <param-name>shiroEnvironmentClass</param-name>
- <param-value>org.opendaylight.aaa.shiro.web.env.KarafIniWebEnvironment</param-value>
+ <param-name>shiroEnvironmentClass</param-name>
+ <param-value>org.opendaylight.aaa.shiro.web.env.KarafIniWebEnvironment</param-value>
</context-param>
<listener>
<url-pattern>/apis/*</url-pattern>
</servlet-mapping>
+ <servlet-mapping>
+ <servlet-name>JAXRSApiDoc</servlet-name>
+ <url-pattern>/17/apis/*</url-pattern>
+ </servlet-mapping>
+
<filter>
<filter-name>cross-origin-api-doc</filter-name>
<filter-class>org.eclipse.jetty.servlets.CrossOriginFilter</filter-class>
<filter-name>cross-origin-api-doc</filter-name>
<url-pattern>/apis/*</url-pattern>
</filter-mapping>
-
+ <filter-mapping>
+ <filter-name>cross-origin-api-doc</filter-name>
+ <url-pattern>/17/apis/*</url-pattern>
+ </filter-mapping>
<security-constraint>
- <web-resource-collection>
- <web-resource-name>API Doc</web-resource-name>
- <url-pattern>/*</url-pattern>
- </web-resource-collection>
+ <web-resource-collection>
+ <web-resource-name>API Doc</web-resource-name>
+ <url-pattern>/*</url-pattern>
+ </web-resource-collection>
</security-constraint>
</web-app>
import org.opendaylight.yangtools.yang.model.api.Module;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
-/**
- *
- */
public class ApiDocGeneratorTest {
public static final String HTTP_HOST = "http://host";
@Before
public void setUp() throws Exception {
this.generator = new ApiDocGenerator();
+ generator.setDraft(false);
this.helper = new DocGenTestHelper();
this.helper.setUp();
*/
private void validateSwaggerApisForPost(final ApiDeclaration doc) {
// two POST URI with concrete schema name in summary
- final Api lstApi = findApi("/config/toaster2:lst/", doc);
- assertNotNull("Api /config/toaster2:lst/ wasn't found", lstApi);
+ final Api lstApi = findApi("/config/toaster2:lst", doc);
+ assertNotNull("Api /config/toaster2:lst wasn't found", lstApi);
assertTrue("POST for cont1 in lst is missing",
findOperation(lstApi.getOperations(), "POST", "(config)lstPOST", "toaster2/lst(config)lst1-TOP",
"toaster2/lst(config)cont1-TOP"));
- final Api cont1Api = findApi("/config/toaster2:lst/cont1/", doc);
- assertNotNull("Api /config/toaster2:lst/cont1/ wasn't found", cont1Api);
+ final Api cont1Api = findApi("/config/toaster2:lst/cont1", doc);
+ assertNotNull("Api /config/toaster2:lst/cont1 wasn't found", cont1Api);
assertTrue("POST for cont11 in cont1 is missing",
findOperation(cont1Api.getOperations(), "POST", "(config)cont1POST", "toaster2/lst/cont1(config)cont11-TOP",
"toaster2/lst/cont1(config)lst11-TOP"));
// no POST URI
- final Api cont11Api = findApi("/config/toaster2:lst/cont1/cont11/", doc);
- assertNotNull("Api /config/toaster2:lst/cont1/cont11/ wasn't found", cont11Api);
+ final Api cont11Api = findApi("/config/toaster2:lst/cont1/cont11", doc);
+ assertNotNull("Api /config/toaster2:lst/cont1/cont11 wasn't found", cont11Api);
assertTrue("POST operation shouldn't be present.", findOperations(cont11Api.getOperations(), "POST").isEmpty());
}
* Tries to find operation with name {@code operationName} and with summary {@code summary}
*/
private boolean findOperation(final List<Operation> operations, final String operationName, final String type,
- final String... searchedParameters) {
+ final String... searchedParameters) {
final Set<Operation> filteredOperations = findOperations(operations, operationName);
for (final Operation operation : filteredOperations) {
if (operation.getType().equals(type)) {
final JSONObject configLstTop = models.getJSONObject("toaster2(config)lst-TOP");
assertNotNull(configLstTop);
- containsReferences(configLstTop, "lst", "toaster2(config)");
+ containsReferences(configLstTop, "toaster2:lst", "toaster2(config)");
final JSONObject configLst = models.getJSONObject("toaster2(config)lst");
assertNotNull(configLst);
- containsReferences(configLst, "lst1", "toaster2/lst(config)");
- containsReferences(configLst, "cont1", "toaster2/lst(config)");
+ containsReferences(configLst, "toaster2:lst1", "toaster2/lst(config)");
+ containsReferences(configLst, "toaster2:cont1", "toaster2/lst(config)");
final JSONObject configLst1Top = models.getJSONObject("toaster2/lst(config)lst1-TOP");
assertNotNull(configLst1Top);
- containsReferences(configLst1Top, "lst1", "toaster2/lst(config)");
+ containsReferences(configLst1Top, "toaster2:lst1", "toaster2/lst(config)");
final JSONObject configLst1 = models.getJSONObject("toaster2/lst(config)lst1");
assertNotNull(configLst1);
final JSONObject configCont1Top = models.getJSONObject("toaster2/lst(config)cont1-TOP");
assertNotNull(configCont1Top);
- containsReferences(configCont1Top, "cont1", "toaster2/lst(config)");
+ containsReferences(configCont1Top, "toaster2:cont1", "toaster2/lst(config)");
final JSONObject configCont1 = models.getJSONObject("toaster2/lst(config)cont1");
assertNotNull(configCont1);
- containsReferences(configCont1, "cont11", "toaster2/lst/cont1(config)");
- containsReferences(configCont1, "lst11", "toaster2/lst/cont1(config)");
+ containsReferences(configCont1, "toaster2:cont11", "toaster2/lst/cont1(config)");
+ containsReferences(configCont1, "toaster2:lst11", "toaster2/lst/cont1(config)");
final JSONObject configCont11Top = models.getJSONObject("toaster2/lst/cont1(config)cont11-TOP");
assertNotNull(configCont11Top);
- containsReferences(configCont11Top, "cont11", "toaster2/lst/cont1(config)");
+ containsReferences(configCont11Top, "toaster2:cont11", "toaster2/lst/cont1(config)");
final JSONObject configCont11 = models.getJSONObject("toaster2/lst/cont1(config)cont11");
assertNotNull(configCont11);
final JSONObject configlst11Top = models.getJSONObject("toaster2/lst/cont1(config)lst11-TOP");
assertNotNull(configlst11Top);
- containsReferences(configlst11Top, "lst11", "toaster2/lst/cont1(config)");
+ containsReferences(configlst11Top, "toaster2:lst11", "toaster2/lst/cont1(config)");
final JSONObject configLst11 = models.getJSONObject("toaster2/lst/cont1(config)lst11");
assertNotNull(configLst11);
} catch (final JSONException e) {
assertNotNull(itemsInNodeInProperties);
final String itemRef = itemsInNodeInProperties.getString("$ref");
- assertEquals(prefix + childObject, itemRef);
+ assertEquals(prefix + childObject.split(":")[1], itemRef);
}
@Test
// testing bugs.opendaylight.org bug 1290. UnionType model type.
final String jsonString = doc.getModels().toString();
assertTrue(jsonString.contains(
- "testUnion\":{\"type\":\"integer or string\",\"required\":false}"));
+ "testUnion\":{\"type\":\"-2147483648\",\"required\":false,\"enum\":[\"-2147483648\",\"Some testUnion\"]}"));
}
}
}
final JSONObject models = doc.getModels();
final JSONObject inputTop = models.getJSONObject("(make-toast)input-TOP");
- final String testString = "{\"input\":{\"type\":\"object\",\"items\":{\"$ref\":\"(make-toast)input\"}}}";
+ final String testString = "{\"toaster:input\":{\"type\":\"object\",\"items\":{\"$ref\":\"(make-toast)input\"}}}";
assertEquals(testString, inputTop.getJSONObject("properties").toString());
final JSONObject input = models.getJSONObject("(make-toast)input");
final JSONObject properties = input.getJSONObject("properties");
- assertTrue(properties.has("toasterDoneness"));
- assertTrue(properties.has("toasterToastType"));
+ assertTrue(properties.has("toaster:toasterDoneness"));
+ assertTrue(properties.has("toaster:toasterToastType"));
}
}
}
* @throws Exception
*/
private void validateToaster(final ApiDeclaration doc) throws Exception {
- final Set<String> expectedUrls = new TreeSet<>(Arrays.asList(new String[] { "/config/toaster2:toaster/",
- "/operational/toaster2:toaster/", "/operations/toaster2:cancel-toast",
+ final Set<String> expectedUrls = new TreeSet<>(Arrays.asList(new String[]{"/config/toaster2:toaster",
+ "/operational/toaster2:toaster", "/operations/toaster2:cancel-toast",
"/operations/toaster2:make-toast", "/operations/toaster2:restock-toaster",
- "/config/toaster2:toaster/toasterSlot/{slotId}/toaster-augmented:slotInfo/" }));
+ "/config/toaster2:toaster/toasterSlot/{slotId}/toaster-augmented:slotInfo"}));
final Set<String> actualUrls = new TreeSet<>();
final JSONObject configToaster = topLevelJson.getJSONObject("toaster2(config)toaster");
assertNotNull("(config)toaster JSON object missing", configToaster);
// without module prefix
- containsProperties(configToaster, "toasterSlot");
+ containsProperties(configToaster, "toaster2:toasterSlot");
final JSONObject toasterSlot = topLevelJson.getJSONObject("toaster2/toaster(config)toasterSlot");
assertNotNull("(config)toasterSlot JSON object missing", toasterSlot);
// with module prefix
- containsProperties(toasterSlot, "toaster-augmented:slotInfo");
+ containsProperties(toasterSlot, "toaster2:toaster-augmented:slotInfo");
} catch (final JSONException e) {
fail("Json exception while reading JSON object. Original message " + e.getMessage());
assertNotNull(property + " is missing", concretePropertyObject);
}
}
-}
+}
\ No newline at end of file
.node(QName.create("nodes"))
.node(QName.create("node"))
.nodeWithKey(QName.create("node"), QName.create("id"), "123").build();
- private static final String INSTANCE_URL = "nodes/node/123/";
+ private static final String INSTANCE_URL = "/nodes/node/123/";
private MountPointSwagger swagger;
private DocGenTestHelper helper;
private SchemaContext schemaContext;
final UriInfo mockInfo = setUpSwaggerForDocGeneration();
this.swagger.onMountPointCreated(instanceId); // add this ID into the list of
// mount points
+
final ApiDeclaration mountPointApi = this.swagger.getMountPointApi(mockInfo, 1L, "Datastores", "-");
assertNotNull("failed to find Datastore API", mountPointApi);
final List<Api> apis = mountPointApi.getApis();
.getNotes());
}
final Set<String> expectedApis = new TreeSet<>(Arrays.asList(new String[] {
- "/config/" + INSTANCE_URL + "yang-ext:mount/",
- "/operational/" + INSTANCE_URL + "yang-ext:mount/",
- "/operations/" + INSTANCE_URL + "yang-ext:mount/", }));
+ "/config" + INSTANCE_URL + "yang-ext:mount",
+ "/operational" + INSTANCE_URL + "yang-ext:mount",
+ "/operations" + INSTANCE_URL + "yang-ext:mount",}));
assertEquals(expectedApis, actualApis);
}