From: Ed Warnicke Date: Thu, 5 Jun 2014 13:19:27 +0000 (+0000) Subject: Merge "Bug 1073: Implemented Transaction chain on InMemoryDOMDataStore level." X-Git-Tag: release/helium~711 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=11161d86d92f769c85de7e7c8621a4f7ad1bb6f9;hp=761a2b50e10129c5f6ccb59df940b5dd39d6e424 Merge "Bug 1073: Implemented Transaction chain on InMemoryDOMDataStore level." --- diff --git a/opendaylight/commons/opendaylight/pom.xml b/opendaylight/commons/opendaylight/pom.xml index c166f668cc..cb21a1cbe7 100644 --- a/opendaylight/commons/opendaylight/pom.xml +++ b/opendaylight/commons/opendaylight/pom.xml @@ -107,7 +107,7 @@ 1.1-SNAPSHOT 1.9.5 0.2.5-SNAPSHOT - 4.0.17.Final + 4.0.19.Final 0.0.3-SNAPSHOT 0.4.2-SNAPSHOT 0.4.2-SNAPSHOT diff --git a/opendaylight/config/logback-config-loader/pom.xml b/opendaylight/config/logback-config-loader/pom.xml new file mode 100644 index 0000000000..03ff65f662 --- /dev/null +++ b/opendaylight/config/logback-config-loader/pom.xml @@ -0,0 +1,53 @@ + + + + 4.0.0 + + org.opendaylight.controller + config-plugin-parent + 0.2.5-SNAPSHOT + ../config-plugin-parent + + logback-config-loader + bundle + ${project.artifactId} + + 3.0.4 + + + + + ch.qos.logback + logback-classic + + + ch.qos.logback + logback-core + + + org.slf4j + slf4j-api + + + + + junit + junit + test + + + + + + + org.apache.felix + maven-bundle-plugin + + + org.opendaylight.controller.logback.config.loader.Activator + + + + + + diff --git a/opendaylight/config/logback-config-loader/src/main/java/org/opendaylight/controller/logback/config/loader/Activator.java b/opendaylight/config/logback-config-loader/src/main/java/org/opendaylight/controller/logback/config/loader/Activator.java new file mode 100644 index 0000000000..99866d5767 --- /dev/null +++ b/opendaylight/config/logback-config-loader/src/main/java/org/opendaylight/controller/logback/config/loader/Activator.java @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2014 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.controller.logback.config.loader; + +import java.io.File; +import java.util.List; + +import org.opendaylight.controller.logback.config.loader.impl.LogbackConfigUtil; +import org.opendaylight.controller.logback.config.loader.impl.LogbackConfigurationLoader; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * default activator for loading multiple logback configuration files + */ +public class Activator implements BundleActivator { + + /** + * expected environment variable name, containing the root folder containing + * logback configurations + */ + private static final String LOGBACK_CONFIG_D = "logback.config.d"; + private static Logger LOG = LoggerFactory.getLogger(Activator.class); + + @Override + public void start(BundleContext context) { + LOG.info("Starting logback configuration loader"); + String logbackConfigRoot = System.getProperty(LOGBACK_CONFIG_D); + LOG.debug("configRoot: {}", logbackConfigRoot); + if (logbackConfigRoot != null) { + File logbackConfigRootFile = new File(logbackConfigRoot); + List sortedConfigFiles = LogbackConfigUtil.harvestSortedConfigFiles(logbackConfigRootFile); + LogbackConfigurationLoader.load(true, sortedConfigFiles.toArray()); + } + } + + @Override + public void stop(BundleContext context) { + LOG.info("Stopping logback configuration loader"); + // TODO: need reset/reload default config? + } + +} diff --git a/opendaylight/config/logback-config-loader/src/main/java/org/opendaylight/controller/logback/config/loader/impl/LogbackConfigUtil.java b/opendaylight/config/logback-config-loader/src/main/java/org/opendaylight/controller/logback/config/loader/impl/LogbackConfigUtil.java new file mode 100644 index 0000000000..ddf14d7dd3 --- /dev/null +++ b/opendaylight/config/logback-config-loader/src/main/java/org/opendaylight/controller/logback/config/loader/impl/LogbackConfigUtil.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2014 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.controller.logback.config.loader.impl; + +import java.io.File; +import java.io.FileFilter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.regex.Pattern; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * logback config utils + */ +public final class LogbackConfigUtil { + + /** logback config file pattern (*.xml) */ + protected static final String LOGBACK_CONFIG_FILE_REGEX_SEED = ".+\\.xml"; + private static final Logger LOG = LoggerFactory + .getLogger(LogbackConfigUtil.class); + + /** + * forbidden ctor + */ + private LogbackConfigUtil() { + throw new UnsupportedOperationException(); + } + + /** + * @param logConfigRoot folder containing configuration files + * @return sorted list of found files + */ + public static List harvestSortedConfigFiles(File logConfigRoot) { + final Pattern xmlFilePattern = Pattern.compile(LOGBACK_CONFIG_FILE_REGEX_SEED); + File[] configs = logConfigRoot.listFiles(new FileFilter() { + @Override + public boolean accept(File pathname) { + return pathname.isFile() + && xmlFilePattern.matcher(pathname.getName()).find(); + } + }); + + List sortedConfigFiles = new ArrayList(configs.length); + for (File cfgItem : configs) { + LOG.trace("config: {}", cfgItem.toURI()); + sortedConfigFiles.add(cfgItem); + } + Collections.sort(sortedConfigFiles); + + return sortedConfigFiles; + } + +} diff --git a/opendaylight/config/logback-config-loader/src/main/java/org/opendaylight/controller/logback/config/loader/impl/LogbackConfigurationLoader.java b/opendaylight/config/logback-config-loader/src/main/java/org/opendaylight/controller/logback/config/loader/impl/LogbackConfigurationLoader.java new file mode 100644 index 0000000000..2aa6b1a348 --- /dev/null +++ b/opendaylight/config/logback-config-loader/src/main/java/org/opendaylight/controller/logback/config/loader/impl/LogbackConfigurationLoader.java @@ -0,0 +1,109 @@ +/** + * Copyright (c) 201 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.controller.logback.config.loader.impl; + +import java.io.File; +import java.net.URL; + +import org.slf4j.ILoggerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.joran.JoranConfigurator; +import ch.qos.logback.core.joran.spi.JoranException; +import ch.qos.logback.core.util.StatusPrinter; + +/** + * Logback configuration loader. + * Strategy: + *
    + *
  1. reset actual configuration (probably default configuration)
  2. + *
  3. load all given logback config xml files in given order
  4. + *
+ */ +public final class LogbackConfigurationLoader { + + private static final Logger LOG = LoggerFactory + .getLogger(LogbackConfigurationLoader.class); + + /** + * forbidden ctor + */ + private LogbackConfigurationLoader() { + throw new UnsupportedOperationException(); + } + + /** + * load given logback configurations in given order, reset existing configuration before applying first one + * @param purgeBefore require reset before loading first config + * @param args + */ + public static void load(boolean purgeBefore, Object...args) { + try { + if (purgeBefore) { + resetExistingConfiguration(); + } + for (Object logbackConfig : args) { + load(logbackConfig); + } + } catch (IllegalStateException e) { + LOG.warn("loading of multiple logback configurations failed", e); + } + } + + /** + * purge existing logback configuration + */ + public static void resetExistingConfiguration() { + LOG.trace("resetting existing logback configuration"); + LoggerContext context = getLoggerContext(); + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext(context); + context.reset(); + } + + /** + * @return logback context + */ + private static LoggerContext getLoggerContext() { + ILoggerFactory context = LoggerFactory.getILoggerFactory(); + if (context != null && context instanceof LoggerContext) { + // now SLF4J is bound to logback in the current environment + return (LoggerContext) context; + } + throw new IllegalStateException("current logger factory is not supported: " + context); + } + + /** + * @param logbackConfig + * @param reset true if previous configuration needs to get purged + */ + public static void load(Object logbackConfig) { + LOG.trace("BEFORE logback reconfig"); + try { + LoggerContext context = getLoggerContext(); + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext(context); + if (logbackConfig instanceof String) { + configurator.doConfigure((String) logbackConfig); + } else if (logbackConfig instanceof URL) { + configurator.doConfigure((URL) logbackConfig); + } else if (logbackConfig instanceof File) { + configurator.doConfigure((File) logbackConfig); + } + + LOG.trace("applied {}", logbackConfig); + StatusPrinter.printInCaseOfErrorsOrWarnings(context); + } catch (IllegalStateException | JoranException je) { + LOG.warn("Logback configuration loading failed: {}", logbackConfig); + } + LOG.trace("AFTER logback reconfig"); + } + +} diff --git a/opendaylight/config/logback-config-loader/src/test/java/org/opendaylight/controller/logback/config/loader/test/LogbackConfigurationLoaderTest.java b/opendaylight/config/logback-config-loader/src/test/java/org/opendaylight/controller/logback/config/loader/test/LogbackConfigurationLoaderTest.java new file mode 100644 index 0000000000..2e9bf1df00 --- /dev/null +++ b/opendaylight/config/logback-config-loader/src/test/java/org/opendaylight/controller/logback/config/loader/test/LogbackConfigurationLoaderTest.java @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2014 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.controller.logback.config.loader.test; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.opendaylight.controller.logback.config.loader.impl.LogbackConfigUtil; +import org.opendaylight.controller.logback.config.loader.impl.LogbackConfigurationLoader; +import org.opendaylight.controller.logback.config.loader.test.logwork.Debugger; +import org.opendaylight.controller.logback.config.loader.test.logwork.Errorer; +import org.opendaylight.controller.logback.config.loader.test.logwork.Informer; +import org.opendaylight.controller.logback.config.loader.test.logwork.Tracer; +import org.opendaylight.controller.logback.config.loader.test.logwork.Warner; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * test of logging config loader - {@link LogbackConfigurationLoader} + */ +@RunWith(JUnit4.class) +public class LogbackConfigurationLoaderTest { + + /** logback config root */ + private static final String LOGBACK_D = "/logback.d"; + private static Logger LOG = LoggerFactory + .getLogger(LogbackConfigurationLoaderTest.class); + + /** + * Test of method {@link LogbackConfigurationLoader#load(boolean, Object[])} + * + * @throws Exception + */ + @Test + public void testLoad() throws Exception { + File logConfigRoot = new File(LogbackConfigurationLoaderTest.class + .getResource(LOGBACK_D).getFile()); + List sortedConfigFiles = LogbackConfigUtil.harvestSortedConfigFiles(logConfigRoot); + LogbackConfigurationLoader.load(true, sortedConfigFiles.toArray()); + + LOG.info("LOGBACK ready -> about to use it"); + + Tracer.doSomeAction(); + Debugger.doSomeAction(); + Informer.doSomeAction(); + Warner.doSomeAction(); + Errorer.doSomeAction(); + + // check logs + String[] expectedLogs = new String[] { + "LoggingEvent -> [INFO] org.opendaylight.controller.logback.config.loader.test.LogbackConfigurationLoaderTest: LOGBACK ready -> about to use it", + "LoggingEvent -> [TRACE] org.opendaylight.controller.logback.config.loader.test.logwork.Tracer: tracing", + "LoggingEvent -> [DEBUG] org.opendaylight.controller.logback.config.loader.test.logwork.Tracer: debugging", + "LoggingEvent -> [INFO] org.opendaylight.controller.logback.config.loader.test.logwork.Tracer: infoing", + "LoggingEvent -> [WARN] org.opendaylight.controller.logback.config.loader.test.logwork.Tracer: warning", + "LoggingEvent -> [ERROR] org.opendaylight.controller.logback.config.loader.test.logwork.Tracer: erroring", + "LoggingEvent -> [DEBUG] org.opendaylight.controller.logback.config.loader.test.logwork.Debugger: debugging", + "LoggingEvent -> [INFO] org.opendaylight.controller.logback.config.loader.test.logwork.Debugger: infoing", + "LoggingEvent -> [WARN] org.opendaylight.controller.logback.config.loader.test.logwork.Debugger: warning", + "LoggingEvent -> [ERROR] org.opendaylight.controller.logback.config.loader.test.logwork.Debugger: erroring", + "LoggingEvent -> [INFO] org.opendaylight.controller.logback.config.loader.test.logwork.Informer: infoing", + "LoggingEvent -> [WARN] org.opendaylight.controller.logback.config.loader.test.logwork.Informer: warning", + "LoggingEvent -> [ERROR] org.opendaylight.controller.logback.config.loader.test.logwork.Informer: erroring", + "LoggingEvent -> [WARN] org.opendaylight.controller.logback.config.loader.test.logwork.Warner: warning", + "LoggingEvent -> [ERROR] org.opendaylight.controller.logback.config.loader.test.logwork.Warner: erroring", + "LoggingEvent -> [ERROR] org.opendaylight.controller.logback.config.loader.test.logwork.Errorer: erroring" + + }; + + List logSnapshot = new ArrayList<>(TestAppender.getLogRecord()); + for (String logLine : logSnapshot) { + LOG.info("\"{}\",", logLine); + } + + Assert.assertArrayEquals(expectedLogs, logSnapshot.toArray()); + } +} diff --git a/opendaylight/config/logback-config-loader/src/test/java/org/opendaylight/controller/logback/config/loader/test/TestAppender.java b/opendaylight/config/logback-config-loader/src/test/java/org/opendaylight/controller/logback/config/loader/test/TestAppender.java new file mode 100644 index 0000000000..b273d2777c --- /dev/null +++ b/opendaylight/config/logback-config-loader/src/test/java/org/opendaylight/controller/logback/config/loader/test/TestAppender.java @@ -0,0 +1,145 @@ +/** + * Copyright (c) 2014 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.controller.logback.config.loader.test; + +import java.util.ArrayList; +import java.util.List; + +import ch.qos.logback.classic.spi.LoggingEvent; +import ch.qos.logback.core.Appender; +import ch.qos.logback.core.Context; +import ch.qos.logback.core.LogbackException; +import ch.qos.logback.core.filter.Filter; +import ch.qos.logback.core.spi.FilterReply; +import ch.qos.logback.core.status.Status; + +/** + * dummy appender for collecting log messages + * + * @param + */ +public class TestAppender implements Appender { + + private boolean started; + private Context context; + private String name; + + private static List logRecord = new ArrayList<>(); + + @Override + public void start() { + started = true; + } + + @Override + public void stop() { + started = false; + } + + @Override + public boolean isStarted() { + return started; + } + + @Override + public void setContext(Context context) { + this.context = context; + } + + @Override + public Context getContext() { + return context; + } + + @Override + public void addStatus(Status status) { + // TODO Auto-generated method stub + } + + @Override + public void addInfo(String msg) { + // TODO Auto-generated method stub + } + + @Override + public void addInfo(String msg, Throwable ex) { + // TODO Auto-generated method stub + } + + @Override + public void addWarn(String msg) { + // TODO Auto-generated method stub + } + + @Override + public void addWarn(String msg, Throwable ex) { + // TODO Auto-generated method stub + } + + @Override + public void addError(String msg) { + // TODO Auto-generated method stub + } + + @Override + public void addError(String msg, Throwable ex) { + // TODO Auto-generated method stub + } + + @Override + public void addFilter(Filter newFilter) { + // TODO Auto-generated method stub + } + + @Override + public void clearAllFilters() { + // TODO Auto-generated method stub + } + + @Override + public List> getCopyOfAttachedFiltersList() { + // TODO Auto-generated method stub + return null; + } + + @Override + public FilterReply getFilterChainDecision(E event) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getName() { + return name; + } + + @Override + public void doAppend(E event) throws LogbackException { + if (event instanceof LoggingEvent) { + LoggingEvent lEvent = (LoggingEvent) event; + logRecord.add(String.format("%s -> [%s] %s: %s", event.getClass() + .getSimpleName(), lEvent.getLevel(), + lEvent.getLoggerName(), lEvent.getMessage())); + } else { + logRecord.add(event.getClass() + " -> " + event.toString()); + } + } + + @Override + public void setName(String name) { + this.name = name; + } + + /** + * @return the logRecord + */ + public static List getLogRecord() { + return logRecord; + } + +} diff --git a/opendaylight/config/logback-config-loader/src/test/java/org/opendaylight/controller/logback/config/loader/test/logwork/Debugger.java b/opendaylight/config/logback-config-loader/src/test/java/org/opendaylight/controller/logback/config/loader/test/logwork/Debugger.java new file mode 100644 index 0000000000..a8052f71c9 --- /dev/null +++ b/opendaylight/config/logback-config-loader/src/test/java/org/opendaylight/controller/logback/config/loader/test/logwork/Debugger.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2014 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.controller.logback.config.loader.test.logwork; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * dummy logging guy + */ +public class Debugger { + + private static Logger LOG = LoggerFactory.getLogger(Debugger.class); + + /** + * all logging + */ + public static void doSomeAction() { + LOG.trace("tracing"); + LOG.debug("debugging"); + LOG.info("infoing"); + LOG.warn("warning"); + LOG.error("erroring"); + } + +} diff --git a/opendaylight/config/logback-config-loader/src/test/java/org/opendaylight/controller/logback/config/loader/test/logwork/Errorer.java b/opendaylight/config/logback-config-loader/src/test/java/org/opendaylight/controller/logback/config/loader/test/logwork/Errorer.java new file mode 100644 index 0000000000..0bcd830ad1 --- /dev/null +++ b/opendaylight/config/logback-config-loader/src/test/java/org/opendaylight/controller/logback/config/loader/test/logwork/Errorer.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2014 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.controller.logback.config.loader.test.logwork; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * dummy logging guy + */ +public class Errorer { + + private static Logger LOG = LoggerFactory.getLogger(Errorer.class); + + /** + * all logging + */ + public static void doSomeAction() { + LOG.trace("tracing"); + LOG.debug("debugging"); + LOG.info("infoing"); + LOG.warn("warning"); + LOG.error("erroring"); + } + +} diff --git a/opendaylight/config/logback-config-loader/src/test/java/org/opendaylight/controller/logback/config/loader/test/logwork/Informer.java b/opendaylight/config/logback-config-loader/src/test/java/org/opendaylight/controller/logback/config/loader/test/logwork/Informer.java new file mode 100644 index 0000000000..44f09315cf --- /dev/null +++ b/opendaylight/config/logback-config-loader/src/test/java/org/opendaylight/controller/logback/config/loader/test/logwork/Informer.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2014 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.controller.logback.config.loader.test.logwork; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * dummy logging guy + */ +public class Informer { + + private static Logger LOG = LoggerFactory.getLogger(Informer.class); + + /** + * all logging + */ + public static void doSomeAction() { + LOG.trace("tracing"); + LOG.debug("debugging"); + LOG.info("infoing"); + LOG.warn("warning"); + LOG.error("erroring"); + } + +} diff --git a/opendaylight/config/logback-config-loader/src/test/java/org/opendaylight/controller/logback/config/loader/test/logwork/Tracer.java b/opendaylight/config/logback-config-loader/src/test/java/org/opendaylight/controller/logback/config/loader/test/logwork/Tracer.java new file mode 100644 index 0000000000..70df607d8d --- /dev/null +++ b/opendaylight/config/logback-config-loader/src/test/java/org/opendaylight/controller/logback/config/loader/test/logwork/Tracer.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2014 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.controller.logback.config.loader.test.logwork; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * dummy logging guy + */ +public class Tracer { + + private static Logger LOG = LoggerFactory.getLogger(Tracer.class); + + /** + * all logging + */ + public static void doSomeAction() { + LOG.trace("tracing"); + LOG.debug("debugging"); + LOG.info("infoing"); + LOG.warn("warning"); + LOG.error("erroring"); + } + +} diff --git a/opendaylight/config/logback-config-loader/src/test/java/org/opendaylight/controller/logback/config/loader/test/logwork/Warner.java b/opendaylight/config/logback-config-loader/src/test/java/org/opendaylight/controller/logback/config/loader/test/logwork/Warner.java new file mode 100644 index 0000000000..8093180a8b --- /dev/null +++ b/opendaylight/config/logback-config-loader/src/test/java/org/opendaylight/controller/logback/config/loader/test/logwork/Warner.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2014 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.controller.logback.config.loader.test.logwork; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * dummy logging guy + */ +public class Warner { + + private static Logger LOG = LoggerFactory.getLogger(Warner.class); + + /** + * all logging + */ + public static void doSomeAction() { + LOG.trace("tracing"); + LOG.debug("debugging"); + LOG.info("infoing"); + LOG.warn("warning"); + LOG.error("erroring"); + } + +} diff --git a/opendaylight/config/logback-config-loader/src/test/resources/logback-test.xml b/opendaylight/config/logback-config-loader/src/test/resources/logback-test.xml new file mode 100755 index 0000000000..7fb760aca7 --- /dev/null +++ b/opendaylight/config/logback-config-loader/src/test/resources/logback-test.xml @@ -0,0 +1,17 @@ + + + + + + %date{"yyyy-MM-dd HH:mm:ss.SSS z"} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + diff --git a/opendaylight/config/logback-config-loader/src/test/resources/logback.d/logback-alt.xml b/opendaylight/config/logback-config-loader/src/test/resources/logback.d/logback-alt.xml new file mode 100755 index 0000000000..ca489d5c78 --- /dev/null +++ b/opendaylight/config/logback-config-loader/src/test/resources/logback.d/logback-alt.xml @@ -0,0 +1,19 @@ + + + + + + + %date{"yyyy-MM-dd HH:mm:ss.SSS z"} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + diff --git a/opendaylight/config/logback-config-loader/src/test/resources/logback.d/logback-alt2.xml b/opendaylight/config/logback-config-loader/src/test/resources/logback.d/logback-alt2.xml new file mode 100755 index 0000000000..89f82c5046 --- /dev/null +++ b/opendaylight/config/logback-config-loader/src/test/resources/logback.d/logback-alt2.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/opendaylight/config/logback-config-loader/src/test/resources/logback.d/logback-alt3.xml b/opendaylight/config/logback-config-loader/src/test/resources/logback.d/logback-alt3.xml new file mode 100755 index 0000000000..a37b6f7f21 --- /dev/null +++ b/opendaylight/config/logback-config-loader/src/test/resources/logback.d/logback-alt3.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/opendaylight/config/logback-config/src/test/java/org/opendaylight/controller/config/yang/logback/config/LogbackModuleTest.java b/opendaylight/config/logback-config/src/test/java/org/opendaylight/controller/config/yang/logback/config/LogbackModuleTest.java index d9c9dada62..75323d256e 100644 --- a/opendaylight/config/logback-config/src/test/java/org/opendaylight/controller/config/yang/logback/config/LogbackModuleTest.java +++ b/opendaylight/config/logback-config/src/test/java/org/opendaylight/controller/config/yang/logback/config/LogbackModuleTest.java @@ -7,6 +7,14 @@ */ package org.opendaylight.controller.config.yang.logback.config; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.junit.matchers.JUnitMatchers.containsString; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import javax.management.ObjectName; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -16,15 +24,6 @@ import org.opendaylight.controller.config.manager.impl.AbstractConfigTest; import org.opendaylight.controller.config.manager.impl.factoriesresolver.HardcodedModuleFactoriesResolver; import org.opendaylight.controller.config.util.ConfigTransactionJMXClient; -import javax.management.ObjectName; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; -import static org.junit.matchers.JUnitMatchers.containsString; - public class LogbackModuleTest extends AbstractConfigTest { private static final String INSTANCE_NAME = "singleton"; @@ -89,7 +88,7 @@ public class LogbackModuleTest extends AbstractConfigTest { assertBeanCount(1, factory.getImplementationName()); ConfigTransactionJMXClient transaction = configRegistryClient.createTransaction(); - transaction.destroyConfigBean(factory.getImplementationName(), INSTANCE_NAME); + transaction.destroyModule(factory.getImplementationName(), INSTANCE_NAME); CommitStatus status = transaction.commit(); assertBeanCount(0, factory.getImplementationName()); diff --git a/opendaylight/config/threadpool-config-impl/src/test/java/org/opendaylight/controller/config/threadpool/fixed/FixedThreadPoolConfigBeanTest.java b/opendaylight/config/threadpool-config-impl/src/test/java/org/opendaylight/controller/config/threadpool/fixed/FixedThreadPoolConfigBeanTest.java index c95661d9c9..af7f01a632 100644 --- a/opendaylight/config/threadpool-config-impl/src/test/java/org/opendaylight/controller/config/threadpool/fixed/FixedThreadPoolConfigBeanTest.java +++ b/opendaylight/config/threadpool-config-impl/src/test/java/org/opendaylight/controller/config/threadpool/fixed/FixedThreadPoolConfigBeanTest.java @@ -7,6 +7,15 @@ */ package org.opendaylight.controller.config.threadpool.fixed; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.junit.matchers.JUnitMatchers.containsString; + +import java.lang.management.ManagementFactory; +import javax.management.InstanceAlreadyExistsException; +import javax.management.InstanceNotFoundException; +import javax.management.ObjectName; import org.junit.Before; import org.junit.Test; import org.opendaylight.controller.config.api.ConflictingVersionException; @@ -20,14 +29,6 @@ import org.opendaylight.controller.config.yang.threadpool.impl.NamingThreadFacto import org.opendaylight.controller.config.yang.threadpool.impl.fixed.FixedThreadPoolModuleFactory; import org.opendaylight.controller.config.yang.threadpool.impl.fixed.FixedThreadPoolModuleMXBean; -import javax.management.InstanceAlreadyExistsException; -import javax.management.InstanceNotFoundException; -import javax.management.ObjectName; - -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; -import static org.junit.matchers.JUnitMatchers.containsString; - public class FixedThreadPoolConfigBeanTest extends AbstractConfigTest { private FixedThreadPoolModuleFactory factory; @@ -92,17 +93,24 @@ public class FixedThreadPoolConfigBeanTest extends AbstractConfigTest { @Test public void testDestroy() throws InstanceAlreadyExistsException, ValidationException, ConflictingVersionException, InstanceNotFoundException { - ConfigTransactionJMXClient transaction = configRegistryClient.createTransaction(); - createFixed(transaction, nameInstance, 1); + int numberOfThreads = 100; + int threadCount1 = ManagementFactory.getThreadMXBean().getThreadCount(); + assertTrue("Expected less than " + numberOfThreads + " threads, got " + threadCount1, threadCount1 < numberOfThreads); + ConfigTransactionJMXClient transaction = configRegistryClient.createTransaction(); + createFixed(transaction, nameInstance, numberOfThreads); transaction.commit(); + int threadCount2 = ManagementFactory.getThreadMXBean().getThreadCount(); + assertTrue("Expected more than " + numberOfThreads + " threads, got " + threadCount2, threadCount2 > numberOfThreads); transaction = configRegistryClient.createTransaction(); - transaction.destroyConfigBean(factory.getImplementationName(), nameInstance); + transaction.destroyModule(factory.getImplementationName(), nameInstance); CommitStatus status = transaction.commit(); assertBeanCount(0, factory.getImplementationName()); assertStatus(status, 0, 0, 1); + int threadCount3 = ManagementFactory.getThreadMXBean().getThreadCount(); + assertTrue("Expected less than " + numberOfThreads + " threads, got " + threadCount3, threadCount3 < numberOfThreads); } @Test diff --git a/opendaylight/config/threadpool-config-impl/src/test/java/org/opendaylight/controller/config/threadpool/scheduled/ScheduledThreadPoolConfigBeanTest.java b/opendaylight/config/threadpool-config-impl/src/test/java/org/opendaylight/controller/config/threadpool/scheduled/ScheduledThreadPoolConfigBeanTest.java index ef06e43d2f..0fc2fd6eb3 100644 --- a/opendaylight/config/threadpool-config-impl/src/test/java/org/opendaylight/controller/config/threadpool/scheduled/ScheduledThreadPoolConfigBeanTest.java +++ b/opendaylight/config/threadpool-config-impl/src/test/java/org/opendaylight/controller/config/threadpool/scheduled/ScheduledThreadPoolConfigBeanTest.java @@ -7,6 +7,15 @@ */ package org.opendaylight.controller.config.threadpool.scheduled; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.junit.matchers.JUnitMatchers.containsString; + +import javax.management.InstanceAlreadyExistsException; +import javax.management.InstanceNotFoundException; +import javax.management.ObjectName; import org.junit.Before; import org.junit.Test; import org.opendaylight.controller.config.api.ConflictingVersionException; @@ -20,16 +29,6 @@ import org.opendaylight.controller.config.yang.threadpool.impl.NamingThreadFacto import org.opendaylight.controller.config.yang.threadpool.impl.scheduled.ScheduledThreadPoolModuleFactory; import org.opendaylight.controller.config.yang.threadpool.impl.scheduled.ScheduledThreadPoolModuleMXBean; -import javax.management.InstanceAlreadyExistsException; -import javax.management.InstanceNotFoundException; -import javax.management.ObjectName; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.junit.matchers.JUnitMatchers.containsString; - public class ScheduledThreadPoolConfigBeanTest extends AbstractConfigTest { private ScheduledThreadPoolModuleFactory factory; @@ -103,7 +102,7 @@ public class ScheduledThreadPoolConfigBeanTest extends AbstractConfigTest { transaction.commit(); transaction = configRegistryClient.createTransaction(); - transaction.destroyConfigBean(factory.getImplementationName(), instanceName); + transaction.destroyModule(factory.getImplementationName(), instanceName); CommitStatus status = transaction.commit(); assertBeanCount(0, factory.getImplementationName()); diff --git a/opendaylight/distribution/opendaylight/src/main/resources/configuration/initial/99-netconf-connector.xml b/opendaylight/distribution/opendaylight/src/main/resources/configuration/initial/99-netconf-connector.xml index fcbec19f5f..b4b433dbca 100644 --- a/opendaylight/distribution/opendaylight/src/main/resources/configuration/initial/99-netconf-connector.xml +++ b/opendaylight/distribution/opendaylight/src/main/resources/configuration/initial/99-netconf-connector.xml @@ -28,6 +28,24 @@ prefix:netty-timer global-timer + + + + + prefix:threadfactory-naming + global-netconf-processing-executor-threadfactory + remote-connector-processing-executor + + + prefix:threadpool-flexible + global-netconf-processing-executor + 1 + 4 + 600000 + + prefix:threadfactory + global-netconf-processing-executor-threadfactory + @@ -43,6 +61,10 @@ prefix:netty-event-executor global-event-executor + + prefix:binding-broker-osgi-registry + binding-osgi-broker + prefix:dom-broker-osgi-registry dom-broker @@ -51,6 +73,10 @@ prefix:netconf-client-dispatcher global-netconf-dispatcher + + prefix:threadpool + global-netconf-processing-executor + @@ -62,6 +88,20 @@ /modules/module[type='netconf-client-dispatcher'][name='global-netconf-dispatcher'] + + prefix:threadfactory + + global-netconf-processing-executor-threadfactory + /modules/module[type='threadfactory-naming'][name='global-netconf-processing-executor-threadfactory'] + + + + prefix:threadpool + + global-netconf-processing-executor + /modules/module[type='threadpool-flexible'][name='global-netconf-processing-executor'] + + @@ -69,5 +109,7 @@ urn:opendaylight:params:xml:ns:yang:controller:md:sal:connector:netconf?module=odl-sal-netconf-connector-cfg&revision=2013-10-28 urn:opendaylight:params:xml:ns:yang:controller:config:netconf:client:dispatcher?module=odl-netconfig-client-cfg&revision=2014-04-08 + urn:opendaylight:params:xml:ns:yang:controller:threadpool:impl?module=threadpool-impl&revision=2013-04-05 + urn:opendaylight:params:xml:ns:yang:controller:threadpool:impl:flexible?module=threadpool-impl-flexible&revision=2013-12-01 diff --git a/opendaylight/md-sal/model/model-flow-base/src/main/yang/opendaylight-match-types.yang b/opendaylight/md-sal/model/model-flow-base/src/main/yang/opendaylight-match-types.yang index b02b0dc25c..efe1ce3e3a 100644 --- a/opendaylight/md-sal/model/model-flow-base/src/main/yang/opendaylight-match-types.yang +++ b/opendaylight/md-sal/model/model-flow-base/src/main/yang/opendaylight-match-types.yang @@ -269,6 +269,12 @@ module opendaylight-match-types { } } + grouping "tcp-flag-match-fields" { + leaf tcp-flag { + type uint16; + } + } + grouping match { leaf in-port { type inv:node-connector-id; @@ -340,5 +346,9 @@ module opendaylight-match-types { container "protocol-match-fields" { uses "protocol-match-fields"; } + + container tcp-flag-match { + uses "tcp-flag-match-fields"; + } } } \ No newline at end of file diff --git a/opendaylight/md-sal/model/model-flow-base/src/main/yang/opendaylight-table-types.yang b/opendaylight/md-sal/model/model-flow-base/src/main/yang/opendaylight-table-types.yang index e74b548342..c271f8f4d0 100644 --- a/opendaylight/md-sal/model/model-flow-base/src/main/yang/opendaylight-table-types.yang +++ b/opendaylight/md-sal/model/model-flow-base/src/main/yang/opendaylight-table-types.yang @@ -188,6 +188,10 @@ module opendaylight-table-types { base match-field; description "Match for IPv6 Extension Header pseudo-field"; } + identity tcp_flag { + base match-field; + description "TCP Flag Match"; + } grouping set-field-match { list set-field-match { diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/AbstractNotificationListenerRegistration.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/AbstractNotificationListenerRegistration.java new file mode 100644 index 0000000000..5e7c91374f --- /dev/null +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/AbstractNotificationListenerRegistration.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2014 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.controller.sal.binding.impl; + +import org.opendaylight.controller.sal.binding.api.NotificationListener; +import org.opendaylight.yangtools.concepts.AbstractListenerRegistration; +import org.opendaylight.yangtools.yang.binding.Notification; + +import com.google.common.base.Preconditions; + +/** + * Abstract implementation of {@link NotificationListenerRegistration}. + * + * @param Notification type + */ +abstract class AbstractNotificationListenerRegistration extends AbstractListenerRegistration> implements NotificationListenerRegistration { + private final Class type; + + protected AbstractNotificationListenerRegistration(final Class type, final NotificationListener listener) { + super(listener); + this.type = Preconditions.checkNotNull(type); + } + + @Override + public Class getType() { + return type; + } + + @Override + @SuppressWarnings("unchecked") + public void notify(final Notification notification) { + if (!isClosed()) { + getInstance().onNotification((T)notification); + } + } +} diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/AggregatedNotificationListenerRegistration.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/AggregatedNotificationListenerRegistration.java new file mode 100644 index 0000000000..f0db891f14 --- /dev/null +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/AggregatedNotificationListenerRegistration.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2014 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.controller.sal.binding.impl; + +import org.opendaylight.controller.sal.binding.api.NotificationListener; +import org.opendaylight.yangtools.yang.binding.Notification; + +import com.google.common.base.Preconditions; + +/** + * An aggregated listener registration. This is a result of registering an invoker which can handle multiple + * interfaces at the same time. In order to support correct delivery, we need to maintain per-type registrations + * which get squashed if a notification which implements multiple interfaces is encountered. + * + * We take care of that by implementing alternate {@link #hashCode()}/{@link #equals(Object)}, which resolve + * to the backing aggregator. + * + * @param Notification type + * @param Aggregator type + */ +abstract class AggregatedNotificationListenerRegistration extends AbstractNotificationListenerRegistration { + private final A aggregator; + + protected AggregatedNotificationListenerRegistration(final Class type, final NotificationListener listener, final A aggregator) { + super(type, listener); + this.aggregator = Preconditions.checkNotNull(aggregator); + } + + protected A getAggregator() { + return aggregator; + } + + @Override + public int hashCode() { + return aggregator.hashCode(); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!this.getClass().equals(obj.getClass())) { + return false; + } + + return aggregator.equals(((AggregatedNotificationListenerRegistration)obj).aggregator); + } +} diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/ListenerMapGeneration.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/ListenerMapGeneration.java new file mode 100644 index 0000000000..4d893aa7be --- /dev/null +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/ListenerMapGeneration.java @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2014 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.controller.sal.binding.impl; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import org.opendaylight.yangtools.yang.binding.Notification; + +import com.google.common.base.Predicate; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Multimap; + +/** + * An immutable view of the current generation of listeners. + */ +final class ListenerMapGeneration { + private static final int CACHE_MAX_ENTRIES = 1000; + + /** + * Constant map of notification type to subscribed listeners. + */ + private final Multimap, NotificationListenerRegistration> typeToListeners; + + /** + * Dynamic cache of notification implementation to matching listeners. This cache loads entries based on + * the contents of the {@link #typeToListeners} map. + */ + private final LoadingCache, Iterable>> implementationToListeners = + CacheBuilder.newBuilder() + .weakKeys() + .maximumSize(CACHE_MAX_ENTRIES) + .build(new CacheLoader, Iterable>>() { + @Override + public Iterable> load(final Class key) { + final Set> regs = new HashSet<>(); + + for (final Class type : getNotificationTypes(key)) { + @SuppressWarnings("unchecked") + final Collection> l = typeToListeners.get((Class) type); + if (l != null) { + regs.addAll(l); + } + } + + return ImmutableSet.copyOf(regs); + } + }); + + ListenerMapGeneration() { + typeToListeners = ImmutableMultimap.of(); + } + + ListenerMapGeneration(final Multimap, NotificationListenerRegistration> listeners) { + this.typeToListeners = ImmutableMultimap.copyOf(listeners); + } + + /** + * Current listeners. Exposed for creating the next generation. + * + * @return Current type-to-listener map. + */ + Multimap, NotificationListenerRegistration> getListeners() { + return typeToListeners; + } + + /** + * Look up the listeners which need to see this notification delivered. + * + * @param notification Notification object + * @return Iterable of listeners, guaranteed to be nonnull. + */ + public Iterable> listenersFor(final Notification notification) { + // Safe to use, as our loader does not throw checked exceptions + return implementationToListeners.getUnchecked(notification.getClass()); + } + + public Iterable> getKnownTypes() { + return typeToListeners.keySet(); + } + + private static Iterable> getNotificationTypes(final Class cls) { + final Class[] ifaces = cls.getInterfaces(); + return Iterables.filter(Arrays.asList(ifaces), new Predicate>() { + @Override + public boolean apply(final Class input) { + if (Notification.class.equals(input)) { + return false; + } + return Notification.class.isAssignableFrom(input); + } + }); + } +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/MountPointManagerImpl.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/MountPointManagerImpl.java index b999a6f01c..df09f78620 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/MountPointManagerImpl.java +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/MountPointManagerImpl.java @@ -11,10 +11,10 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.opendaylight.controller.md.sal.binding.util.AbstractBindingSalProviderInstance; -import org.opendaylight.yangtools.concepts.util.ListenerRegistry; import org.opendaylight.controller.sal.binding.api.mount.MountProviderInstance; import org.opendaylight.controller.sal.binding.api.mount.MountProviderService; import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yangtools.concepts.util.ListenerRegistry; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,7 +27,7 @@ public class MountPointManagerImpl implements MountProviderService { private final ConcurrentMap, BindingMountPointImpl> mountPoints; private final ListenerRegistry listeners = ListenerRegistry.create(); - + private ListeningExecutorService notificationExecutor; private ListeningExecutorService dataCommitExecutor; @@ -39,7 +39,7 @@ public class MountPointManagerImpl implements MountProviderService { return notificationExecutor; } - public void setNotificationExecutor(ListeningExecutorService notificationExecutor) { + public void setNotificationExecutor(final ListeningExecutorService notificationExecutor) { this.notificationExecutor = notificationExecutor; } @@ -47,12 +47,12 @@ public class MountPointManagerImpl implements MountProviderService { return dataCommitExecutor; } - public void setDataCommitExecutor(ListeningExecutorService dataCommitExecutor) { + public void setDataCommitExecutor(final ListeningExecutorService dataCommitExecutor) { this.dataCommitExecutor = dataCommitExecutor; } @Override - public synchronized BindingMountPointImpl createMountPoint(InstanceIdentifier path) { + public synchronized BindingMountPointImpl createMountPoint(final InstanceIdentifier path) { BindingMountPointImpl potential = mountPoints.get(path); if (potential != null) { throw new IllegalStateException("Mount point already exists."); @@ -61,7 +61,7 @@ public class MountPointManagerImpl implements MountProviderService { } @Override - public BindingMountPointImpl createOrGetMountPoint(InstanceIdentifier path) { + public BindingMountPointImpl createOrGetMountPoint(final InstanceIdentifier path) { BindingMountPointImpl potential = getMountPoint(path); if (potential != null) { return potential; @@ -70,18 +70,17 @@ public class MountPointManagerImpl implements MountProviderService { } @Override - public BindingMountPointImpl getMountPoint(InstanceIdentifier path) { + public BindingMountPointImpl getMountPoint(final InstanceIdentifier path) { return mountPoints.get(path); } - private synchronized BindingMountPointImpl createOrGetMountPointImpl(InstanceIdentifier path) { + private synchronized BindingMountPointImpl createOrGetMountPointImpl(final InstanceIdentifier path) { BindingMountPointImpl potential = getMountPoint(path); if (potential != null) { return potential; } RpcProviderRegistryImpl rpcRegistry = new RpcProviderRegistryImpl("mount"); - NotificationBrokerImpl notificationBroker = new NotificationBrokerImpl(); - notificationBroker.setExecutor(getNotificationExecutor()); + NotificationBrokerImpl notificationBroker = new NotificationBrokerImpl(getNotificationExecutor()); DataBrokerImpl dataBroker = new DataBrokerImpl(); dataBroker.setExecutor(getDataCommitExecutor()); BindingMountPointImpl mountInstance = new BindingMountPointImpl(path, rpcRegistry, notificationBroker, @@ -91,7 +90,7 @@ public class MountPointManagerImpl implements MountProviderService { return mountInstance; } - private void notifyMountPointCreated(InstanceIdentifier path) { + private void notifyMountPointCreated(final InstanceIdentifier path) { for (ListenerRegistration listener : listeners) { try { listener.getInstance().onMountPointCreated(path); @@ -102,28 +101,28 @@ public class MountPointManagerImpl implements MountProviderService { } @Override - public ListenerRegistration registerProvisionListener(MountProvisionListener listener) { + public ListenerRegistration registerProvisionListener(final MountProvisionListener listener) { return listeners.register(listener); } public class BindingMountPointImpl extends - AbstractBindingSalProviderInstance + AbstractBindingSalProviderInstance implements MountProviderInstance { - private InstanceIdentifier identifier; + private final InstanceIdentifier identifier; - public BindingMountPointImpl(org.opendaylight.yangtools.yang.binding.InstanceIdentifier identifier, - RpcProviderRegistryImpl rpcRegistry, NotificationBrokerImpl notificationBroker, - DataBrokerImpl dataBroker) { + public BindingMountPointImpl(final org.opendaylight.yangtools.yang.binding.InstanceIdentifier identifier, + final RpcProviderRegistryImpl rpcRegistry, final NotificationBrokerImpl notificationBroker, + final DataBrokerImpl dataBroker) { super(rpcRegistry, notificationBroker, dataBroker); this.identifier = identifier; } // Needed only for BI Connector public DataBrokerImpl getDataBrokerImpl() { - return (DataBrokerImpl) getDataBroker(); + return getDataBroker(); } - + @Override public InstanceIdentifier getIdentifier() { return this.identifier; diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/NotificationBrokerImpl.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/NotificationBrokerImpl.java new file mode 100644 index 0000000000..258ba51777 --- /dev/null +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/NotificationBrokerImpl.java @@ -0,0 +1,159 @@ +/** + * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.sal.binding.impl; + +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicReference; + +import javax.annotation.concurrent.GuardedBy; + +import org.opendaylight.controller.sal.binding.api.NotificationListener; +import org.opendaylight.controller.sal.binding.api.NotificationProviderService; +import org.opendaylight.controller.sal.binding.codegen.impl.SingletonHolder; +import org.opendaylight.controller.sal.binding.spi.NotificationInvokerFactory.NotificationInvoker; +import org.opendaylight.yangtools.concepts.AbstractListenerRegistration; +import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yangtools.concepts.util.ListenerRegistry; +import org.opendaylight.yangtools.yang.binding.Notification; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; + +public class NotificationBrokerImpl implements NotificationProviderService, AutoCloseable { + private static final Logger LOG = LoggerFactory.getLogger(NotificationBrokerImpl.class); + + private final ListenerRegistry interestListeners = + ListenerRegistry.create(); + private final AtomicReference listeners = new AtomicReference<>(new ListenerMapGeneration()); + private final ExecutorService executor; + + public NotificationBrokerImpl(final ExecutorService executor) { + this.executor = Preconditions.checkNotNull(executor); + } + + @Override + public void publish(final Notification notification) { + publish(notification, executor); + } + + @Override + public void publish(final Notification notification, final ExecutorService service) { + for (NotificationListenerRegistration r : listeners.get().listenersFor(notification)) { + service.submit(new NotifyTask(r, notification)); + } + } + + @GuardedBy("this") + private Multimap, NotificationListenerRegistration> mutableListeners() { + return HashMultimap.create(listeners.get().getListeners()); + } + + private final void addRegistrations(final NotificationListenerRegistration... registrations) { + synchronized (this) { + final Multimap, NotificationListenerRegistration> newListeners = + mutableListeners(); + for (NotificationListenerRegistration reg : registrations) { + newListeners.put(reg.getType(), reg); + } + + listeners.set(new ListenerMapGeneration(newListeners)); + } + + // Notifications are dispatched out of lock... + for (NotificationListenerRegistration reg : registrations) { + announceNotificationSubscription(reg.getType()); + } + } + + private synchronized void removeRegistrations(final NotificationListenerRegistration... registrations) { + final Multimap, NotificationListenerRegistration> newListeners = + mutableListeners(); + + for (NotificationListenerRegistration reg : registrations) { + newListeners.remove(reg.getType(), reg); + } + + listeners.set(new ListenerMapGeneration(newListeners)); + } + + private void announceNotificationSubscription(final Class notification) { + for (final ListenerRegistration listener : interestListeners) { + try { + listener.getInstance().onNotificationSubscribtion(notification); + } catch (Exception e) { + LOG.warn("Listener {} reported unexpected error on notification {}", + listener.getInstance(), notification, e); + } + } + } + + @Override + public ListenerRegistration registerInterestListener(final NotificationInterestListener interestListener) { + final ListenerRegistration registration = this.interestListeners.register(interestListener); + + for (final Class notification : listeners.get().getKnownTypes()) { + interestListener.onNotificationSubscribtion(notification); + } + return registration; + } + + @Override + public NotificationListenerRegistration registerNotificationListener(final Class notificationType, final NotificationListener listener) { + final NotificationListenerRegistration reg = new AbstractNotificationListenerRegistration(notificationType, listener) { + @Override + protected void removeRegistration() { + removeRegistrations(this); + } + }; + + addRegistrations(reg); + return reg; + } + + @Override + public ListenerRegistration registerNotificationListener(final org.opendaylight.yangtools.yang.binding.NotificationListener listener) { + final NotificationInvoker invoker = SingletonHolder.INVOKER_FACTORY.invokerFor(listener); + final Set> types = invoker.getSupportedNotifications(); + final NotificationListenerRegistration[] regs = new NotificationListenerRegistration[types.size()]; + + // Populate the registrations... + int i = 0; + for (Class type : types) { + regs[i] = new AggregatedNotificationListenerRegistration(type, invoker.getInvocationProxy(), regs) { + @Override + protected void removeRegistration() { + // Nothing to do, will be cleaned up by parent (below) + } + }; + ++i; + } + + // ... now put them to use ... + addRegistrations(regs); + + // ... finally return the parent registration + return new AbstractListenerRegistration(listener) { + @Override + protected void removeRegistration() { + removeRegistrations(regs); + for (ListenerRegistration reg : regs) { + reg.close(); + } + } + }; + } + + @Override + public void close() { + } + +} diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/NotificationBrokerImpl.xtend b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/NotificationBrokerImpl.xtend deleted file mode 100644 index 6d675b4b5e..0000000000 --- a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/NotificationBrokerImpl.xtend +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.controller.sal.binding.impl - -import com.google.common.collect.HashMultimap -import com.google.common.collect.ImmutableSet -import com.google.common.collect.Multimap -import com.google.common.collect.Multimaps -import java.util.Collections -import java.util.concurrent.Callable -import java.util.concurrent.ExecutorService -import java.util.concurrent.Future -import java.util.Set -import org.opendaylight.controller.sal.binding.api.NotificationListener -import org.opendaylight.controller.sal.binding.api.NotificationProviderService -import org.opendaylight.controller.sal.binding.api.NotificationProviderService.NotificationInterestListener -import org.opendaylight.controller.sal.binding.codegen.impl.SingletonHolder -import org.opendaylight.controller.sal.binding.spi.NotificationInvokerFactory.NotificationInvoker -import org.opendaylight.yangtools.concepts.AbstractObjectRegistration -import org.opendaylight.yangtools.concepts.ListenerRegistration -import org.opendaylight.yangtools.concepts.Registration -import org.opendaylight.yangtools.concepts.util.ListenerRegistry -import org.opendaylight.yangtools.yang.binding.Notification -import org.slf4j.LoggerFactory - -class NotificationBrokerImpl implements NotificationProviderService, AutoCloseable { - - val ListenerRegistry interestListeners = ListenerRegistry.create; - - val Multimap, NotificationListener> listeners; - - @Property - var ExecutorService executor; - - val logger = LoggerFactory.getLogger(NotificationBrokerImpl) - - new() { - listeners = Multimaps.synchronizedSetMultimap(HashMultimap.create()) - } - - @Deprecated - new(ExecutorService executor) { - listeners = Multimaps.synchronizedSetMultimap(HashMultimap.create()) - this.executor = executor; - } - - def getNotificationTypes(Notification notification) { - notification.class.interfaces.filter[it != Notification && Notification.isAssignableFrom(it)] - } - - override publish(Notification notification) { - publish(notification, executor) - } - - override publish(Notification notification, ExecutorService service) { - val allTypes = notification.notificationTypes - - var Iterable> listenerToNotify = Collections.emptySet(); - for (type : allTypes) { - listenerToNotify = listenerToNotify + listeners.get(type as Class) - } - val tasks = listenerToNotify.map[new NotifyTask(it, notification)].toSet; - submitAll(executor,tasks); - } - - def submitAll(ExecutorService service, Set tasks) { - val ret = ImmutableSet.>builder(); - for(task : tasks) { - ret.add(service.submit(task)); - } - return ret.build(); - } - - override registerNotificationListener(Class notificationType, - NotificationListener listener) { - val reg = new GenericNotificationRegistration(notificationType, listener, this); - listeners.put(notificationType, listener); - announceNotificationSubscription(notificationType); - return reg; - } - - def announceNotificationSubscription(Class notification) { - for (listener : interestListeners) { - try { - listener.instance.onNotificationSubscribtion(notification); - } catch (Exception e) { - logger.error("", e.message) - } - } - } - - override registerNotificationListener( - org.opendaylight.yangtools.yang.binding.NotificationListener listener) { - val invoker = SingletonHolder.INVOKER_FACTORY.invokerFor(listener); - for (notifyType : invoker.supportedNotifications) { - listeners.put(notifyType, invoker.invocationProxy) - announceNotificationSubscription(notifyType) - } - val registration = new GeneratedListenerRegistration(listener, invoker,this); - return registration as Registration; - } - - protected def unregisterListener(GenericNotificationRegistration reg) { - listeners.remove(reg.type, reg.instance); - } - - protected def unregisterListener(GeneratedListenerRegistration reg) { - for (notifyType : reg.invoker.supportedNotifications) { - listeners.remove(notifyType, reg.invoker.invocationProxy) - } - } - - override close() { - //FIXME: implement properly. - } - - override registerInterestListener(NotificationInterestListener interestListener) { - val registration = interestListeners.register(interestListener); - - for(notification : listeners.keySet) { - interestListener.onNotificationSubscribtion(notification); - } - return registration - } -} - -class GenericNotificationRegistration extends AbstractObjectRegistration> implements ListenerRegistration> { - - @Property - val Class type; - - var NotificationBrokerImpl notificationBroker; - - public new(Class type, NotificationListener instance, NotificationBrokerImpl broker) { - super(instance); - _type = type; - notificationBroker = broker; - } - - override protected removeRegistration() { - notificationBroker.unregisterListener(this); - notificationBroker = null; - } -} - -class GeneratedListenerRegistration extends AbstractObjectRegistration implements ListenerRegistration { - - @Property - val NotificationInvoker invoker; - - var NotificationBrokerImpl notificationBroker; - - - new(org.opendaylight.yangtools.yang.binding.NotificationListener instance, NotificationInvoker invoker, NotificationBrokerImpl broker) { - super(instance); - _invoker = invoker; - notificationBroker = broker; - } - - override protected removeRegistration() { - notificationBroker.unregisterListener(this); - notificationBroker = null; - invoker.close(); - } -} - -@Data -class NotifyTask implements Callable { - - private static val log = LoggerFactory.getLogger(NotifyTask); - - @SuppressWarnings("rawtypes") - val NotificationListener listener; - val Notification notification; - - override call() { - //Only logging the complete notification in debug mode - try { - if(log.isDebugEnabled){ - log.debug("Delivering notification {} to {}",notification,listener); - } else { - log.trace("Delivering notification {} to {}",notification.class.name,listener); - } - listener.onNotification(notification); - if(log.isDebugEnabled){ - log.debug("Notification delivered {} to {}",notification,listener); - } else { - log.trace("Notification delivered {} to {}",notification.class.name,listener); - } - } catch (Exception e) { - log.error("Unhandled exception thrown by listener: {}", listener, e); - } - return null; - } - -} diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/NotificationListenerRegistration.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/NotificationListenerRegistration.java new file mode 100644 index 0000000000..3dba868c6f --- /dev/null +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/NotificationListenerRegistration.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2014 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.controller.sal.binding.impl; + +import org.opendaylight.controller.sal.binding.api.NotificationListener; +import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yangtools.yang.binding.Notification; + +/** + * A registration of a {@link NotificationListener}. Allows query of the type + * of the notification and dispatching the notification atomically with regard + * to unregistration. + * + * @param Type of notification + */ +interface NotificationListenerRegistration extends ListenerRegistration> { + /** + * Return the interface class of the notification type. + * + * @return Notification type. + */ + Class getType(); + + /** + * Dispatch a notification to the listener. + * + * @param notification Notification to be dispatched + */ + void notify(Notification notification); +} diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/NotifyTask.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/NotifyTask.java new file mode 100644 index 0000000000..2622a71e55 --- /dev/null +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/NotifyTask.java @@ -0,0 +1,92 @@ +/** + * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.sal.binding.impl; + +import org.opendaylight.yangtools.yang.binding.Notification; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; + +class NotifyTask implements Runnable { + private static final Logger LOG = LoggerFactory.getLogger(NotifyTask.class); + + private final NotificationListenerRegistration registration; + private final Notification notification; + + public NotifyTask(final NotificationListenerRegistration registration, final Notification notification) { + this.registration = Preconditions.checkNotNull(registration); + this.notification = Preconditions.checkNotNull(notification); + } + + @SuppressWarnings("unchecked") + private NotificationListenerRegistration getRegistration() { + return (NotificationListenerRegistration)registration; + } + + @Override + public void run() { + if (LOG.isDebugEnabled()) { + LOG.debug("Delivering notification {} to {}", notification, registration.getInstance()); + } else { + LOG.trace("Delivering notification {} to {}", notification.getClass().getName(), registration.getInstance()); + } + + try { + getRegistration().notify(notification); + } catch (final Exception e) { + LOG.error("Unhandled exception thrown by listener: {}", registration.getInstance(), e); + } + + if (LOG.isDebugEnabled()) { + LOG.debug("Notification delivered {} to {}", notification, registration.getInstance()); + } else { + LOG.trace("Notification delivered {} to {}", notification.getClass().getName(), registration.getInstance()); + } + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((registration== null) ? 0 : registration.hashCode()); + result = prime * result + ((notification== null) ? 0 : notification.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + NotifyTask other = (NotifyTask) obj; + if (registration == null) { + if (other.registration != null) + return false; + } else if (!registration.equals(other.registration)) + return false; + if (notification == null) { + if (other.notification != null) + return false; + } else if (!notification.equals(other.notification)) + return false; + return true; + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("listener", registration) + .add("notification", notification.getClass()) + .toString(); + } +} diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/sal/dom/broker/impl/DataReaderRouter.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/sal/dom/broker/impl/DataReaderRouter.java index 53423f6b09..ba9b2b7f55 100644 --- a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/sal/dom/broker/impl/DataReaderRouter.java +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/sal/dom/broker/impl/DataReaderRouter.java @@ -103,7 +103,7 @@ AbstractDataReadRouter { "Only one simple node for key $s is allowed in node $s", keyValue.getKey(), node); checkState( - simpleNode.get(0).getValue() == keyValue.getValue(), + simpleNode.get(0).getValue().equals(keyValue.getValue()), "Key node must equal to instance identifier value in node $s", node); ret.put(keyValue.getKey(), simpleNode.get(0)); diff --git a/opendaylight/md-sal/sal-netconf-connector/pom.xml b/opendaylight/md-sal/sal-netconf-connector/pom.xml index 9ad95c18d9..0dd25292c2 100644 --- a/opendaylight/md-sal/sal-netconf-connector/pom.xml +++ b/opendaylight/md-sal/sal-netconf-connector/pom.xml @@ -51,6 +51,10 @@ org.opendaylight.controller sal-binding-api + + org.opendaylight.controller + sal-binding-config + org.opendaylight.controller threadpool-config-api @@ -142,11 +146,6 @@ junit test - - org.mockito - mockito-all - test - org.opendaylight.controller logback-config @@ -169,6 +168,11 @@ jar test + + org.opendaylight.yangtools + mockito-configuration + test + diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java index 1839de1597..f73d9cc72f 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java @@ -10,38 +10,29 @@ package org.opendaylight.controller.config.yang.md.sal.connector.netconf; import static org.opendaylight.controller.config.api.JmxAttributeValidationException.checkCondition; import static org.opendaylight.controller.config.api.JmxAttributeValidationException.checkNotNull; -import com.google.common.net.InetAddresses; -import io.netty.util.HashedWheelTimer; -import io.netty.util.concurrent.GlobalEventExecutor; import java.io.File; import java.io.InputStream; import java.net.InetAddress; import java.net.InetSocketAddress; -import java.net.URI; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import org.opendaylight.controller.md.sal.common.api.data.DataChangeEvent; + import org.opendaylight.controller.netconf.client.NetconfClientDispatcher; import org.opendaylight.controller.netconf.client.NetconfClientDispatcherImpl; import org.opendaylight.controller.netconf.client.conf.NetconfClientConfiguration; -import org.opendaylight.controller.netconf.client.conf.NetconfClientConfigurationBuilder; import org.opendaylight.controller.netconf.client.conf.NetconfReconnectingClientConfiguration; import org.opendaylight.controller.netconf.client.conf.NetconfReconnectingClientConfigurationBuilder; import org.opendaylight.controller.netconf.nettyutil.handler.ssh.authentication.LoginPassword; -import org.opendaylight.controller.sal.binding.api.data.DataProviderService; -import org.opendaylight.controller.sal.connect.netconf.InventoryUtils; +import org.opendaylight.controller.sal.binding.api.BindingAwareBroker; +import org.opendaylight.controller.sal.connect.api.RemoteDeviceHandler; import org.opendaylight.controller.sal.connect.netconf.NetconfDevice; -import org.opendaylight.controller.sal.connect.netconf.NetconfDeviceListener; -import org.opendaylight.controller.sal.core.api.data.DataChangeListener; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfDeviceCommunicator; +import org.opendaylight.controller.sal.connect.netconf.sal.NetconfDeviceSalFacade; +import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; +import org.opendaylight.controller.sal.core.api.Broker; import org.opendaylight.protocol.framework.ReconnectStrategy; import org.opendaylight.protocol.framework.ReconnectStrategyFactory; import org.opendaylight.protocol.framework.TimedReconnectStrategy; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.inventory.rev140108.NetconfNode; -import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; -import org.opendaylight.yangtools.yang.common.QName; -import org.opendaylight.yangtools.yang.data.api.CompositeNode; import org.opendaylight.yangtools.yang.model.util.repo.AbstractCachingSchemaSourceProvider; import org.opendaylight.yangtools.yang.model.util.repo.FilesystemSchemaCachingProvider; import org.opendaylight.yangtools.yang.model.util.repo.SchemaSourceProvider; @@ -51,6 +42,10 @@ import org.osgi.framework.ServiceReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.base.Preconditions; +import com.google.common.net.InetAddresses; +import io.netty.util.HashedWheelTimer; + /** * */ @@ -58,22 +53,20 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co { private static final Logger logger = LoggerFactory.getLogger(NetconfConnectorModule.class); - private static ExecutorService GLOBAL_PROCESSING_EXECUTOR = null; private static AbstractCachingSchemaSourceProvider GLOBAL_NETCONF_SOURCE_PROVIDER = null; private BundleContext bundleContext; - public NetconfConnectorModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) { + public NetconfConnectorModule(final org.opendaylight.controller.config.api.ModuleIdentifier identifier, final org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) { super(identifier, dependencyResolver); } - public NetconfConnectorModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver, NetconfConnectorModule oldModule, java.lang.AutoCloseable oldInstance) { + public NetconfConnectorModule(final org.opendaylight.controller.config.api.ModuleIdentifier identifier, final org.opendaylight.controller.config.api.DependencyResolver dependencyResolver, final NetconfConnectorModule oldModule, final java.lang.AutoCloseable oldInstance) { super(identifier, dependencyResolver, oldModule, oldInstance); } @Override protected void customValidation() { checkNotNull(getAddress(), addressJmxAttribute); - //checkState(getAddress().getIpv4Address() != null || getAddress().getIpv6Address() != null,"Address must be set."); checkNotNull(getPort(), portJmxAttribute); checkNotNull(getDomRegistry(), portJmxAttribute); checkNotNull(getDomRegistry(), domRegistryJmxAttribute); @@ -96,41 +89,83 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co checkNotNull(getPassword(), passwordJmxAttribute); } + // FIXME BUG 944 remove this warning + if(getBindingRegistry() == null) { + logger.warn("Configuration property: \"binding-registry\" not set for sal-netconf-connector (" + getIdentifier() + "). " + + "Netconf-connector now requires a dependency on \"binding-broker-osgi-registry\". " + + "The dependency is optional for now to preserve backwards compatibility, but will be mandatory in the future. " + + "Please set the property as in \"01-netconf-connector\" initial config file. " + + "The service will be retrieved from OSGi service registry now."); + } + + // FIXME BUG 944 remove this warning + if(getProcessingExecutor() == null) { + logger.warn("Configuration property: \"processing-executor\" not set for sal-netconf-connector (" + getIdentifier() + "). " + + "Netconf-connector now requires a dependency on \"threadpool\". " + + "The dependency is optional for now to preserve backwards compatibility, but will be mandatory in the future. " + + "Please set the property as in \"01-netconf-connector\" initial config file. " + + "New instance will be created for the executor."); + } } @Override public java.lang.AutoCloseable createInstance() { - ServiceReference serviceReference = bundleContext.getServiceReference(DataProviderService.class); + final RemoteDeviceId id = new RemoteDeviceId(getIdentifier()); - DataProviderService dataProviderService = - bundleContext.getService(serviceReference); + final ExecutorService globalProcessingExecutor = getGlobalProcessingExecutor(); - getDomRegistryDependency(); - NetconfDevice device = new NetconfDevice(getIdentifier().getInstanceName()); + final Broker domBroker = getDomRegistryDependency(); + final BindingAwareBroker bindingBroker = getBindingRegistryBackwards(); - device.setClientConfig(getClientConfig(device)); + final RemoteDeviceHandler salFacade = new NetconfDeviceSalFacade(id, domBroker, bindingBroker, bundleContext, globalProcessingExecutor); + final NetconfDevice device = + NetconfDevice.createNetconfDevice(id, getGlobalNetconfSchemaProvider(), globalProcessingExecutor, salFacade); + final NetconfDeviceCommunicator listener = new NetconfDeviceCommunicator(id, device); + final NetconfReconnectingClientConfiguration clientConfig = getClientConfig(listener); - device.setProcessingExecutor(getGlobalProcessingExecutor()); + // FIXME BUG-944 remove backwards compatibility + final NetconfClientDispatcher dispatcher = getClientDispatcher() == null ? createDispatcher() : getClientDispatcherDependency(); + listener.initializeRemoteConnection(dispatcher, clientConfig); - device.setEventExecutor(getEventExecutorDependency()); - device.setDispatcher(getClientDispatcher() == null ? createDispatcher() : getClientDispatcherDependency()); - device.setSchemaSourceProvider(getGlobalNetconfSchemaProvider(bundleContext)); - device.setDataProviderService(dataProviderService); - getDomRegistryDependency().registerProvider(device, bundleContext); - device.start(); - return device; + return new AutoCloseable() { + @Override + public void close() throws Exception { + listener.close(); + salFacade.close(); + } + }; + } + + private BindingAwareBroker getBindingRegistryBackwards() { + if(getBindingRegistry() != null) { + return getBindingRegistryDependency(); + } else { + // FIXME BUG 944 remove backwards compatibility + final ServiceReference serviceReference = bundleContext.getServiceReference(BindingAwareBroker.class); + Preconditions + .checkNotNull( + serviceReference, + "Unable to retrieve %s from OSGi service registry, use binding-registry config property to inject %s with config subsystem", + BindingAwareBroker.class, BindingAwareBroker.class); + return bundleContext.getService(serviceReference); + } } private ExecutorService getGlobalProcessingExecutor() { - return GLOBAL_PROCESSING_EXECUTOR == null ? Executors.newCachedThreadPool() : GLOBAL_PROCESSING_EXECUTOR; + if(getProcessingExecutor() != null) { + return getProcessingExecutorDependency().getExecutor(); + } else { + // FIXME BUG 944 remove backwards compatibility + return Executors.newCachedThreadPool(); + } } - private synchronized AbstractCachingSchemaSourceProvider getGlobalNetconfSchemaProvider(BundleContext bundleContext) { + private synchronized AbstractCachingSchemaSourceProvider getGlobalNetconfSchemaProvider() { if(GLOBAL_NETCONF_SOURCE_PROVIDER == null) { - String storageFile = "cache/schema"; + final String storageFile = "cache/schema"; // File directory = bundleContext.getDataFile(storageFile); - File directory = new File(storageFile); - SchemaSourceProvider defaultProvider = SchemaSourceProviders.noopProvider(); + final File directory = new File(storageFile); + final SchemaSourceProvider defaultProvider = SchemaSourceProviders.noopProvider(); GLOBAL_NETCONF_SOURCE_PROVIDER = FilesystemSchemaCachingProvider.createFromStringSourceProvider(defaultProvider, directory); } return GLOBAL_NETCONF_SOURCE_PROVIDER; @@ -146,20 +181,20 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co return new NetconfClientDispatcherImpl(getBossThreadGroupDependency(), getWorkerThreadGroupDependency(), new HashedWheelTimer()); } - public void setBundleContext(BundleContext bundleContext) { + public void setBundleContext(final BundleContext bundleContext) { this.bundleContext = bundleContext; } - public NetconfReconnectingClientConfiguration getClientConfig(final NetconfDevice device) { - InetSocketAddress socketAddress = getSocketAddress(); - ReconnectStrategy strategy = getReconnectStrategy(); - long clientConnectionTimeoutMillis = getConnectionTimeoutMillis(); + public NetconfReconnectingClientConfiguration getClientConfig(final NetconfDeviceCommunicator listener) { + final InetSocketAddress socketAddress = getSocketAddress(); + final ReconnectStrategy strategy = getReconnectStrategy(); + final long clientConnectionTimeoutMillis = getConnectionTimeoutMillis(); return NetconfReconnectingClientConfigurationBuilder.create() .withAddress(socketAddress) .withConnectionTimeoutMillis(clientConnectionTimeoutMillis) .withReconnectStrategy(strategy) - .withSessionListener(new NetconfDeviceListener(device)) + .withSessionListener(listener) .withAuthHandler(new LoginPassword(getUsername(),getPassword())) .withProtocol(getTcpOnly() ? NetconfClientConfiguration.NetconfClientProtocol.TCP : @@ -174,19 +209,19 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co } private ReconnectStrategy getReconnectStrategy() { - Long connectionAttempts; + final Long connectionAttempts; if (getMaxConnectionAttempts() != null && getMaxConnectionAttempts() > 0) { connectionAttempts = getMaxConnectionAttempts(); } else { logger.trace("Setting {} on {} to infinity", maxConnectionAttemptsJmxAttribute, this); connectionAttempts = null; } - double sleepFactor = 1.5; - int minSleep = 1000; - Long maxSleep = null; - Long deadline = null; + final double sleepFactor = getSleepFactor().doubleValue(); + final int minSleep = getBetweenAttemptsTimeoutMillis(); + final Long maxSleep = null; + final Long deadline = null; - return new TimedReconnectStrategy(GlobalEventExecutor.INSTANCE, getBetweenAttemptsTimeoutMillis(), + return new TimedReconnectStrategy(getEventExecutorDependency(), getBetweenAttemptsTimeoutMillis(), minSleep, sleepFactor, maxSleep, connectionAttempts, deadline); } @@ -199,7 +234,7 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co addressValue = getAddress().getIpv6Address().getValue(); } */ - InetAddress inetAddress = InetAddresses.forString(getAddress()); + final InetAddress inetAddress = InetAddresses.forString(getAddress()); return new InetSocketAddress(inetAddress, getPort().intValue()); } } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/MessageTransformer.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/MessageTransformer.java new file mode 100644 index 0000000000..7a392a8769 --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/MessageTransformer.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2014 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.controller.sal.connect.api; + +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.model.api.SchemaContextListener; + +public interface MessageTransformer extends SchemaContextListener { + + CompositeNode toNotification(M message); + + M toRpcRequest(QName rpc, CompositeNode node); + + RpcResult toRpcResult(M message, QName rpc); + +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/RemoteDevice.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/RemoteDevice.java new file mode 100644 index 0000000000..e0d24331a7 --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/RemoteDevice.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2014 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.controller.sal.connect.api; + +/** + * + */ +public interface RemoteDevice { + + void onRemoteSessionUp(PREF remoteSessionCapabilities, RemoteDeviceCommunicator listener); + + void onRemoteSessionDown(); + + void onNotification(M notification); +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/RemoteDeviceCommunicator.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/RemoteDeviceCommunicator.java new file mode 100644 index 0000000000..67cb29abcf --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/RemoteDeviceCommunicator.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2014 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.controller.sal.connect.api; + +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.RpcResult; + +import com.google.common.util.concurrent.ListenableFuture; + +public interface RemoteDeviceCommunicator extends AutoCloseable { + + ListenableFuture> sendRequest(M message, QName rpc); + + void close(); +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/RemoteDeviceHandler.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/RemoteDeviceHandler.java new file mode 100644 index 0000000000..b2845d5533 --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/RemoteDeviceHandler.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2014 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.controller.sal.connect.api; + +import org.opendaylight.controller.sal.core.api.RpcImplementation; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.model.api.SchemaContextProvider; + +public interface RemoteDeviceHandler extends AutoCloseable { + + void onDeviceConnected(SchemaContextProvider remoteSchemaContextProvider, + PREF netconfSessionPreferences, RpcImplementation deviceRpc); + + void onDeviceDisconnected(); + + void onNotification(CompositeNode domNotification); + + void close(); +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/SchemaContextProviderFactory.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/SchemaContextProviderFactory.java new file mode 100644 index 0000000000..43577c3c26 --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/SchemaContextProviderFactory.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2014 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.controller.sal.connect.api; + +import java.io.InputStream; +import java.util.Collection; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.model.api.SchemaContextProvider; +import org.opendaylight.yangtools.yang.model.util.repo.SchemaSourceProvider; + +public interface SchemaContextProviderFactory { + + SchemaContextProvider createContextProvider(Collection capabilities, SchemaSourceProvider sourceProvider); + +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/SchemaSourceProviderFactory.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/SchemaSourceProviderFactory.java new file mode 100644 index 0000000000..7037231c5f --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/SchemaSourceProviderFactory.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2014 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.controller.sal.connect.api; + +import org.opendaylight.controller.sal.connect.netconf.sal.NetconfDeviceRpc; +import org.opendaylight.controller.sal.core.api.RpcImplementation; +import org.opendaylight.yangtools.yang.model.util.repo.SchemaSourceProvider; + +public interface SchemaSourceProviderFactory { + + SchemaSourceProvider createSourceProvider(final RpcImplementation deviceRpc); +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/package-info.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/package-info.java new file mode 100644 index 0000000000..022021d46d --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/package-info.java @@ -0,0 +1,6 @@ +/** + * General API for remote connectors e.g. netconf connector + * + * TODO extract into separate bundle when another connector is implemented e.g. restconf connector + */ +package org.opendaylight.controller.sal.connect.api; diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/InventoryUtils.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/InventoryUtils.java deleted file mode 100644 index e6dc59cc10..0000000000 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/InventoryUtils.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2014 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.controller.sal.connect.netconf; - -import java.net.URI; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; - -import org.opendaylight.yangtools.yang.common.QName; -import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class InventoryUtils { - private static final Logger LOG = LoggerFactory.getLogger(InventoryUtils.class); - private static final URI INVENTORY_NAMESPACE = URI.create("urn:opendaylight:inventory"); - private static final URI NETCONF_INVENTORY_NAMESPACE = URI.create("urn:opendaylight:netconf-node-inventory"); - private static final Date INVENTORY_REVISION = dateFromString("2013-08-19"); - private static final Date NETCONF_INVENTORY_REVISION = dateFromString("2014-01-08"); - public static final QName INVENTORY_NODES = new QName(INVENTORY_NAMESPACE, INVENTORY_REVISION, "nodes"); - public static final QName INVENTORY_NODE = new QName(INVENTORY_NAMESPACE, INVENTORY_REVISION, "node"); - public static final QName INVENTORY_ID = new QName(INVENTORY_NAMESPACE, INVENTORY_REVISION, "id"); - public static final QName INVENTORY_CONNECTED = new QName(NETCONF_INVENTORY_NAMESPACE, NETCONF_INVENTORY_REVISION, - "connected"); - public static final QName NETCONF_INVENTORY_INITIAL_CAPABILITY = new QName(NETCONF_INVENTORY_NAMESPACE, - NETCONF_INVENTORY_REVISION, "initial-capability"); - - public static final InstanceIdentifier INVENTORY_PATH = InstanceIdentifier.builder().node(INVENTORY_NODES) - .toInstance(); - public static final QName NETCONF_INVENTORY_MOUNT = null; - - private InventoryUtils() { - throw new UnsupportedOperationException("Utility class cannot be instantiated"); - } - - /** - * Converts date in string format yyyy-MM-dd to java.util.Date. - * - * @return java.util.Date conformant to string formatted date yyyy-MM-dd. - */ - private static Date dateFromString(final String date) { - // We do not reuse the formatter because it's not thread-safe - SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); - try { - return formatter.parse(date); - } catch (ParseException e) { - LOG.error("Failed to parse date {}", date, e); - return null; - } - } -} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDevice.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDevice.java index 94beaed0df..dca8fcafef 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDevice.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDevice.java @@ -7,541 +7,142 @@ */ package org.opendaylight.controller.sal.connect.netconf; -import static com.google.common.base.Preconditions.checkState; -import static org.opendaylight.controller.sal.connect.netconf.InventoryUtils.INVENTORY_CONNECTED; -import static org.opendaylight.controller.sal.connect.netconf.InventoryUtils.INVENTORY_ID; -import static org.opendaylight.controller.sal.connect.netconf.InventoryUtils.INVENTORY_NODE; -import static org.opendaylight.controller.sal.connect.netconf.InventoryUtils.INVENTORY_PATH; -import static org.opendaylight.controller.sal.connect.netconf.InventoryUtils.NETCONF_INVENTORY_INITIAL_CAPABILITY; -import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.CONFIG_SOURCE_RUNNING; -import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_DATA_QNAME; -import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_GET_CONFIG_QNAME; -import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_GET_QNAME; -import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.toFilterStructure; -import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.toRpcMessage; -import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.wrap; - -import com.google.common.base.Preconditions; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; import java.io.InputStream; -import java.net.InetSocketAddress; -import java.net.URI; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; -import org.opendaylight.controller.md.sal.common.api.TransactionStatus; -import org.opendaylight.controller.md.sal.common.api.data.DataCommitHandler; -import org.opendaylight.controller.md.sal.common.api.data.DataModification; -import org.opendaylight.controller.md.sal.common.api.data.DataReader; -import org.opendaylight.controller.netconf.client.NetconfClientDispatcher; -import org.opendaylight.controller.netconf.client.conf.NetconfClientConfiguration; -import org.opendaylight.controller.netconf.client.conf.NetconfReconnectingClientConfiguration; -import org.opendaylight.controller.sal.binding.api.data.DataProviderService; -import org.opendaylight.controller.sal.core.api.Broker.ProviderSession; -import org.opendaylight.controller.sal.core.api.Broker.RpcRegistration; -import org.opendaylight.controller.sal.core.api.Provider; +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.sal.connect.api.MessageTransformer; +import org.opendaylight.controller.sal.connect.api.RemoteDevice; +import org.opendaylight.controller.sal.connect.api.RemoteDeviceCommunicator; +import org.opendaylight.controller.sal.connect.api.RemoteDeviceHandler; +import org.opendaylight.controller.sal.connect.api.SchemaContextProviderFactory; +import org.opendaylight.controller.sal.connect.api.SchemaSourceProviderFactory; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities; +import org.opendaylight.controller.sal.connect.netconf.sal.NetconfDeviceRpc; +import org.opendaylight.controller.sal.connect.netconf.schema.NetconfDeviceSchemaProviderFactory; +import org.opendaylight.controller.sal.connect.netconf.schema.NetconfRemoteSchemaSourceProvider; +import org.opendaylight.controller.sal.connect.netconf.schema.mapping.NetconfMessageTransformer; +import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; import org.opendaylight.controller.sal.core.api.RpcImplementation; -import org.opendaylight.controller.sal.core.api.data.DataBrokerService; -import org.opendaylight.controller.sal.core.api.data.DataModificationTransaction; -import org.opendaylight.controller.sal.core.api.mount.MountProvisionInstance; -import org.opendaylight.controller.sal.core.api.mount.MountProvisionService; -import org.opendaylight.protocol.framework.ReconnectStrategy; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.inventory.rev140108.NetconfNode; -import org.opendaylight.yangtools.concepts.Registration; -import org.opendaylight.yangtools.yang.common.QName; -import org.opendaylight.yangtools.yang.common.RpcResult; import org.opendaylight.yangtools.yang.data.api.CompositeNode; -import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier; -import org.opendaylight.yangtools.yang.data.api.Node; -import org.opendaylight.yangtools.yang.data.api.SimpleNode; -import org.opendaylight.yangtools.yang.data.impl.CompositeNodeTOImpl; -import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode; -import org.opendaylight.yangtools.yang.data.impl.SimpleNodeTOImpl; -import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder; -import org.opendaylight.yangtools.yang.model.api.Module; -import org.opendaylight.yangtools.yang.model.api.RpcDefinition; -import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaContextProvider; import org.opendaylight.yangtools.yang.model.util.repo.AbstractCachingSchemaSourceProvider; import org.opendaylight.yangtools.yang.model.util.repo.SchemaSourceProvider; -import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl; -import org.opendaylight.yangtools.yang.parser.impl.util.YangSourceContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.base.Function; -import com.google.common.base.Optional; -import com.google.common.base.Predicate; -import com.google.common.collect.FluentIterable; -import com.google.common.collect.Iterables; -import com.google.common.util.concurrent.ListenableFuture; -import io.netty.util.concurrent.EventExecutor; - -public class NetconfDevice implements Provider, // - DataReader, // - DataCommitHandler, // - RpcImplementation, // - AutoCloseable { - - InetSocketAddress socketAddress; - - MountProvisionInstance mountInstance; - - EventExecutor eventExecutor; - - ExecutorService processingExecutor; - - InstanceIdentifier path; - - ReconnectStrategy reconnectStrategy; - - AbstractCachingSchemaSourceProvider schemaSourceProvider; - - private NetconfDeviceSchemaContextProvider deviceContextProvider; - - protected Logger logger; - - Registration> operReaderReg; - Registration> confReaderReg; - Registration> commitHandlerReg; - List rpcReg; - - String name; - - MountProvisionService mountService; - - NetconfClientDispatcher dispatcher; - - static InstanceIdentifier ROOT_PATH = InstanceIdentifier.builder().toInstance(); - - SchemaSourceProvider remoteSourceProvider; - - private volatile DataBrokerService dataBroker; - - NetconfDeviceListener listener; - - private boolean rollbackSupported; - - private NetconfReconnectingClientConfiguration clientConfig; - private volatile DataProviderService dataProviderService; - - public NetconfDevice(String name) { - this.name = name; - this.logger = LoggerFactory.getLogger(NetconfDevice.class + "#" + name); - this.path = InstanceIdentifier.builder(INVENTORY_PATH) - .nodeWithKey(INVENTORY_NODE, Collections.singletonMap(INVENTORY_ID, name)).toInstance(); - } - - public void start() { - checkState(dispatcher != null, "Dispatcher must be set."); - checkState(schemaSourceProvider != null, "Schema Source Provider must be set."); - checkState(eventExecutor != null, "Event executor must be set."); - - Preconditions.checkArgument(clientConfig.getSessionListener() instanceof NetconfDeviceListener); - listener = (NetconfDeviceListener) clientConfig.getSessionListener(); - - logger.info("Starting NETCONF Client {} for address {}", name, socketAddress); - - dispatcher.createReconnectingClient(clientConfig); - } - - Optional getSchemaContext() { - if (deviceContextProvider == null) { - return Optional.absent(); - } - return deviceContextProvider.currentContext; - } - - void bringDown() { - if (rpcReg != null) { - for (RpcRegistration reg : rpcReg) { - reg.close(); - } - rpcReg = null; - } - closeGracefully(confReaderReg); - confReaderReg = null; - closeGracefully(operReaderReg); - operReaderReg = null; - closeGracefully(commitHandlerReg); - commitHandlerReg = null; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; - updateDeviceState(false, Collections. emptySet()); - } +/** + * This is a mediator between NetconfDeviceCommunicator and NetconfDeviceSalFacade + */ +public final class NetconfDevice implements RemoteDevice { - private void closeGracefully(final AutoCloseable resource) { - if (resource != null) { - try { - resource.close(); - } catch (Exception e) { - logger.warn("Ignoring exception while closing {}", resource, e); - } - } - } + private static final Logger logger = LoggerFactory.getLogger(NetconfDevice.class); - void bringUp(final SchemaSourceProvider delegate, final Set capabilities, final boolean rollbackSupported) { - // This has to be called from separate thread, not from netty thread calling onSessionUp in DeviceListener. - // Reason: delegate.getSchema blocks thread when waiting for response - // however, if the netty thread is blocked, no incoming message can be processed - // ... netty should pick another thread from pool to process incoming message, but it does not http://netty.io/wiki/thread-model.html - // TODO redesign +refactor - processingExecutor.submit(new Runnable() { - @Override - public void run() { - NetconfDevice.this.rollbackSupported = rollbackSupported; - remoteSourceProvider = schemaSourceProvider.createInstanceFor(delegate); - deviceContextProvider = new NetconfDeviceSchemaContextProvider(NetconfDevice.this, remoteSourceProvider); - deviceContextProvider.createContextFromCapabilities(capabilities); - if (mountInstance != null && getSchemaContext().isPresent()) { - mountInstance.setSchemaContext(getSchemaContext().get()); - } + private final RemoteDeviceId id; - updateDeviceState(true, capabilities); + private final RemoteDeviceHandler salFacade; + private final ListeningExecutorService processingExecutor; + private final MessageTransformer messageTransformer; + private final SchemaContextProviderFactory schemaContextProviderFactory; + private final SchemaSourceProviderFactory sourceProviderFactory; - if (mountInstance != null) { - confReaderReg = mountInstance.registerConfigurationReader(ROOT_PATH, NetconfDevice.this); - operReaderReg = mountInstance.registerOperationalReader(ROOT_PATH, NetconfDevice.this); - commitHandlerReg = mountInstance.registerCommitHandler(ROOT_PATH, NetconfDevice.this); + public static NetconfDevice createNetconfDevice(final RemoteDeviceId id, + final AbstractCachingSchemaSourceProvider schemaSourceProvider, + final ExecutorService executor, final RemoteDeviceHandler salFacade) { - List rpcs = new ArrayList<>(); - // TODO same condition twice - if (mountInstance != null && getSchemaContext().isPresent()) { - for (RpcDefinition rpc : mountInstance.getSchemaContext().getOperations()) { - rpcs.add(mountInstance.addRpcImplementation(rpc.getQName(), NetconfDevice.this)); - } + return new NetconfDevice(id, salFacade, executor, new NetconfMessageTransformer(), + new NetconfDeviceSchemaProviderFactory(id), new SchemaSourceProviderFactory() { + @Override + public SchemaSourceProvider createSourceProvider(final RpcImplementation deviceRpc) { + return schemaSourceProvider.createInstanceFor(new NetconfRemoteSchemaSourceProvider(id, + deviceRpc)); } - rpcReg = rpcs; - } - } - }); - } - - private void updateDeviceState(boolean up, Set capabilities) { - checkDataStoreState(); - - DataModificationTransaction transaction = dataBroker.beginTransaction(); - - CompositeNodeBuilder it = ImmutableCompositeNode.builder(); - it.setQName(INVENTORY_NODE); - it.addLeaf(INVENTORY_ID, name); - it.addLeaf(INVENTORY_CONNECTED, up); - - logger.debug("Client capabilities {}", capabilities); - for (QName capability : capabilities) { - it.addLeaf(NETCONF_INVENTORY_INITIAL_CAPABILITY, capability.toString()); - } - - logger.debug("Update device state transaction " + transaction.getIdentifier() - + " putting operational data started."); - transaction.removeOperationalData(path); - transaction.putOperationalData(path, it.toInstance()); - logger.debug("Update device state transaction " + transaction.getIdentifier() - + " putting operational data ended."); - - // FIXME: this has to be asynchronous - RpcResult transactionStatus = null; - try { - transactionStatus = transaction.commit().get(); - } catch (InterruptedException e) { - throw new RuntimeException("Interrupted while waiting for response", e); - } catch (ExecutionException e) { - throw new RuntimeException("Read configuration data " + path + " failed", e); - } - // TODO better ex handling - - if (transactionStatus.isSuccessful()) { - logger.debug("Update device state transaction " + transaction.getIdentifier() + " SUCCESSFUL."); - } else { - logger.debug("Update device state transaction " + transaction.getIdentifier() + " FAILED!"); - logger.debug("Update device state transaction status " + transaction.getStatus()); - } - } - - @Override - public CompositeNode readConfigurationData(InstanceIdentifier path) { - RpcResult result = null; - try { - result = this.invokeRpc(NETCONF_GET_CONFIG_QNAME, - wrap(NETCONF_GET_CONFIG_QNAME, CONFIG_SOURCE_RUNNING, toFilterStructure(path))).get(); - } catch (InterruptedException e) { - throw new RuntimeException("Interrupted while waiting for response", e); - } catch (ExecutionException e) { - throw new RuntimeException("Read configuration data " + path + " failed", e); - } - - CompositeNode data = result.getResult().getFirstCompositeByName(NETCONF_DATA_QNAME); - return data == null ? null : (CompositeNode) findNode(data, path); - } - - @Override - public CompositeNode readOperationalData(InstanceIdentifier path) { - RpcResult result = null; - try { - result = invokeRpc(NETCONF_GET_QNAME, wrap(NETCONF_GET_QNAME, toFilterStructure(path))).get(); - } catch (InterruptedException e) { - throw new RuntimeException("Interrupted while waiting for response", e); - } catch (ExecutionException e) { - throw new RuntimeException("Read configuration data " + path + " failed", e); - } - - CompositeNode data = result.getResult().getFirstCompositeByName(NETCONF_DATA_QNAME); - return (CompositeNode) findNode(data, path); - } - - @Override - public Set getSupportedRpcs() { - return Collections.emptySet(); - } - - @Override - public ListenableFuture> invokeRpc(QName rpc, CompositeNode input) { - return listener.sendRequest(toRpcMessage(rpc, input, getSchemaContext()), rpc); + }); } - @Override - public Collection getProviderFunctionality() { - return Collections.emptySet(); + @VisibleForTesting + protected NetconfDevice(final RemoteDeviceId id, final RemoteDeviceHandler salFacade, + final ExecutorService processingExecutor, final MessageTransformer messageTransformer, + final SchemaContextProviderFactory schemaContextProviderFactory, + final SchemaSourceProviderFactory sourceProviderFactory) { + this.id = id; + this.messageTransformer = messageTransformer; + this.salFacade = salFacade; + this.sourceProviderFactory = sourceProviderFactory; + this.processingExecutor = MoreExecutors.listeningDecorator(processingExecutor); + this.schemaContextProviderFactory = schemaContextProviderFactory; } @Override - public void onSessionInitiated(ProviderSession session) { - dataBroker = session.getService(DataBrokerService.class); - - processingExecutor.submit(new Runnable() { + public void onRemoteSessionUp(final NetconfSessionCapabilities remoteSessionCapabilities, + final RemoteDeviceCommunicator listener) { + // SchemaContext setup has to be performed in a dedicated thread since + // we are in a netty thread in this method + // Yang models are being downloaded in this method and it would cause a + // deadlock if we used the netty thread + // http://netty.io/wiki/thread-model.html + logger.debug("{}: Session to remote device established with {}", id, remoteSessionCapabilities); + + final ListenableFuture salInitializationFuture = processingExecutor.submit(new Runnable() { @Override public void run() { - updateInitialState(); + final NetconfDeviceRpc deviceRpc = setUpDeviceRpc(remoteSessionCapabilities, listener); + final SchemaSourceProvider delegate = sourceProviderFactory.createSourceProvider(deviceRpc); + final SchemaContextProvider schemaContextProvider = setUpSchemaContext(delegate, remoteSessionCapabilities); + updateMessageTransformer(schemaContextProvider); + salFacade.onDeviceConnected(schemaContextProvider, remoteSessionCapabilities, deviceRpc); } }); - mountService = session.getService(MountProvisionService.class); - if (mountService != null) { - mountInstance = mountService.createOrGetMountPoint(path); - } - } - - private void updateInitialState() { - checkDataStoreState(); - - DataModificationTransaction transaction = dataBroker.beginTransaction(); - if (operationalNodeNotExisting(transaction)) { - transaction.putOperationalData(path, getNodeWithId()); - } - if (configurationNodeNotExisting(transaction)) { - transaction.putConfigurationData(path, getNodeWithId()); - } - - try { - transaction.commit().get(); - } catch (InterruptedException e) { - throw new RuntimeException("Interrupted while waiting for response", e); - } catch (ExecutionException e) { - throw new RuntimeException("Read configuration data " + path + " failed", e); - } - } - - private void checkDataStoreState() { - // read data from Nodes/Node in order to wait with write until schema for Nodes/Node is present in datastore - dataProviderService.readOperationalData(org.opendaylight.yangtools.yang.binding.InstanceIdentifier.builder( - Nodes.class).child(org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node.class).augmentation(NetconfNode.class).build()); } - - CompositeNode getNodeWithId() { - SimpleNodeTOImpl id = new SimpleNodeTOImpl(INVENTORY_ID, null, name); - return new CompositeNodeTOImpl(INVENTORY_NODE, null, Collections.> singletonList(id)); - } - - boolean configurationNodeNotExisting(DataModificationTransaction transaction) { - return null == transaction.readConfigurationData(path); - } - - boolean operationalNodeNotExisting(DataModificationTransaction transaction) { - return null == transaction.readOperationalData(path); - } - - static Node findNode(CompositeNode node, InstanceIdentifier identifier) { - - Node current = node; - for (InstanceIdentifier.PathArgument arg : identifier.getPath()) { - if (current instanceof SimpleNode) { - return null; - } else if (current instanceof CompositeNode) { - CompositeNode currentComposite = (CompositeNode) current; - - current = currentComposite.getFirstCompositeByName(arg.getNodeType()); - if (current == null) { - current = currentComposite.getFirstCompositeByName(arg.getNodeType().withoutRevision()); - } - if (current == null) { - current = currentComposite.getFirstSimpleByName(arg.getNodeType()); - } - if (current == null) { - current = currentComposite.getFirstSimpleByName(arg.getNodeType().withoutRevision()); - } - if (current == null) { - return null; - } - } - } - return current; - } - - @Override - public DataCommitTransaction requestCommit( - DataModification modification) { - NetconfDeviceTwoPhaseCommitTransaction twoPhaseCommit = new NetconfDeviceTwoPhaseCommitTransaction(this, - modification, true, rollbackSupported); - try { - twoPhaseCommit.prepare(); - } catch (InterruptedException e) { - throw new RuntimeException("Interrupted while waiting for response", e); - } catch (ExecutionException e) { - throw new RuntimeException("Read configuration data " + path + " failed", e); - } - return twoPhaseCommit; - } - - Set getCapabilities(Collection capabilities) { - return FluentIterable.from(capabilities).filter(new Predicate() { + Futures.addCallback(salInitializationFuture, new FutureCallback() { @Override - public boolean apply(final String capability) { - return capability.contains("?") && capability.contains("module=") && capability.contains("revision="); + public void onSuccess(final Object result) { + logger.debug("{}: Initialization in sal successful", id); + logger.info("{}: Netconf connector initialized successfully", id); } - }).transform(new Function() { - @Override - public QName apply(final String capability) { - String[] parts = capability.split("\\?"); - String namespace = parts[0]; - FluentIterable queryParams = FluentIterable.from(Arrays.asList(parts[1].split("&"))); - - String revision = getStringAndTransform(queryParams, "revision=", "revision="); - String moduleName = getStringAndTransform(queryParams, "module=", "module="); - - if (revision == null) { - logger.warn("Netconf device was not reporting revision correctly, trying to get amp;revision="); - revision = getStringAndTransform(queryParams, "amp;revision==", "revision="); - - if (revision != null) { - logger.warn("Netconf device returned revision incorectly escaped for {}", capability); - } - } - if (revision == null) { - return QName.create(URI.create(namespace), null, moduleName); - } - return QName.create(namespace, revision, moduleName); - } - - private String getStringAndTransform(final Iterable queryParams, final String match, - final String substringToRemove) { - Optional found = Iterables.tryFind(queryParams, new Predicate() { - @Override - public boolean apply(final String input) { - return input.startsWith(match); - } - }); - - return found.isPresent() ? found.get().replaceAll(substringToRemove, "") : null; + @Override + public void onFailure(final Throwable t) { + // Unable to initialize device, set as disconnected + logger.error("{}: Initialization failed", id, t); + salFacade.onDeviceDisconnected(); } - - }).toSet(); - } - - @Override - public void close() { - bringDown(); - } - - public String getName() { - return name; - } - - public InetSocketAddress getSocketAddress() { - return socketAddress; - } - - public MountProvisionInstance getMountInstance() { - return mountInstance; - } - - public void setReconnectStrategy(final ReconnectStrategy reconnectStrategy) { - this.reconnectStrategy = reconnectStrategy; - } - - public void setProcessingExecutor(final ExecutorService processingExecutor) { - this.processingExecutor = processingExecutor; - } - - public void setSocketAddress(final InetSocketAddress socketAddress) { - this.socketAddress = socketAddress; - } - - public void setEventExecutor(final EventExecutor eventExecutor) { - this.eventExecutor = eventExecutor; - } - - public void setSchemaSourceProvider(final AbstractCachingSchemaSourceProvider schemaSourceProvider) { - this.schemaSourceProvider = schemaSourceProvider; - } - - public void setDispatcher(final NetconfClientDispatcher dispatcher) { - this.dispatcher = dispatcher; + }); } - public void setClientConfig(final NetconfReconnectingClientConfiguration clientConfig) { - this.clientConfig = clientConfig; + /** + * Update initial message transformer to use retrieved schema + */ + private void updateMessageTransformer(final SchemaContextProvider schemaContextProvider) { + messageTransformer.onGlobalContextUpdated(schemaContextProvider.getSchemaContext()); } - public void setDataProviderService(final DataProviderService dataProviderService) { - this.dataProviderService = dataProviderService; + private SchemaContextProvider setUpSchemaContext(final SchemaSourceProvider sourceProvider, final NetconfSessionCapabilities capabilities) { + return schemaContextProviderFactory.createContextProvider(capabilities.getModuleBasedCaps(), sourceProvider); } -} - -class NetconfDeviceSchemaContextProvider { - - NetconfDevice device; - - SchemaSourceProvider sourceProvider; - Optional currentContext; - - NetconfDeviceSchemaContextProvider(NetconfDevice device, SchemaSourceProvider sourceProvider) { - this.device = device; - this.sourceProvider = sourceProvider; - this.currentContext = Optional.absent(); + private NetconfDeviceRpc setUpDeviceRpc(final NetconfSessionCapabilities capHolder, final RemoteDeviceCommunicator listener) { + Preconditions.checkArgument(capHolder.isMonitoringSupported(), + "%s: Netconf device does not support netconf monitoring, yang schemas cannot be acquired. Netconf device capabilities", capHolder); + return new NetconfDeviceRpc(listener, messageTransformer); } - void createContextFromCapabilities(Iterable capabilities) { - YangSourceContext sourceContext = YangSourceContext.createFrom(capabilities, sourceProvider); - if (!sourceContext.getMissingSources().isEmpty()) { - device.logger.warn("Sources for following models are missing {}", sourceContext.getMissingSources()); - } - device.logger.debug("Trying to create schema context from {}", sourceContext.getValidSources()); - List modelsToParse = YangSourceContext.getValidInputStreams(sourceContext); - if (!sourceContext.getValidSources().isEmpty()) { - SchemaContext schemaContext = tryToCreateContext(modelsToParse); - currentContext = Optional.fromNullable(schemaContext); - } else { - currentContext = Optional.absent(); - } - if (currentContext.isPresent()) { - device.logger.debug("Schema context successfully created."); - } + @Override + public void onRemoteSessionDown() { + salFacade.onDeviceDisconnected(); } - SchemaContext tryToCreateContext(List modelsToParse) { - YangParserImpl parser = new YangParserImpl(); - try { - - Set models = parser.parseYangModelsFromStreams(modelsToParse); - return parser.resolveSchemaContext(models); - } catch (Exception e) { - device.logger.debug("Error occured during parsing YANG schemas", e); - return null; - } + @Override + public void onNotification(final NetconfMessage notification) { + final CompositeNode parsedNotification = messageTransformer.toNotification(notification); + salFacade.onNotification(parsedNotification); } } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDeviceListener.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDeviceListener.java deleted file mode 100644 index 68667f0143..0000000000 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDeviceListener.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright (c) 2014 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.controller.sal.connect.netconf; - -import com.google.common.collect.Sets; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.FutureListener; - -import java.util.ArrayDeque; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.Queue; -import java.util.Set; - -import org.opendaylight.controller.netconf.api.NetconfDocumentedException; -import org.opendaylight.controller.netconf.api.NetconfMessage; -import org.opendaylight.controller.netconf.api.NetconfTerminationReason; -import org.opendaylight.controller.netconf.client.NetconfClientSession; -import org.opendaylight.controller.netconf.client.NetconfClientSessionListener; -import org.opendaylight.controller.netconf.util.xml.XmlElement; -import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; -import org.opendaylight.controller.netconf.util.xml.XmlUtil; -import org.opendaylight.controller.sal.common.util.Rpcs; -import org.opendaylight.controller.sal.core.api.mount.MountProvisionInstance; -import org.opendaylight.yangtools.yang.common.QName; -import org.opendaylight.yangtools.yang.common.RpcError; -import org.opendaylight.yangtools.yang.common.RpcResult; -import org.opendaylight.yangtools.yang.data.api.CompositeNode; -import org.opendaylight.yangtools.yang.model.util.repo.SchemaSourceProvider; -import org.opendaylight.yangtools.yang.model.util.repo.SchemaSourceProviders; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.base.Preconditions; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; - -public class NetconfDeviceListener implements NetconfClientSessionListener { - private static final class Request { - final UncancellableFuture> future; - final NetconfMessage request; - final QName rpc; - - private Request(UncancellableFuture> future, NetconfMessage request, final QName rpc) { - this.future = future; - this.request = request; - this.rpc = rpc; - } - } - - private static final Logger LOG = LoggerFactory.getLogger(NetconfDeviceListener.class); - private final Queue requests = new ArrayDeque<>(); - private final NetconfDevice device; - private NetconfClientSession session; - - public NetconfDeviceListener(final NetconfDevice device) { - this.device = Preconditions.checkNotNull(device); - } - - @Override - public synchronized void onSessionUp(final NetconfClientSession session) { - LOG.debug("Session with {} established as address {} session-id {}", - device.getName(), device.getSocketAddress(), session.getSessionId()); - - this.session = session; - - final Set caps = device.getCapabilities(session.getServerCapabilities()); - LOG.trace("Server {} advertized capabilities {}", device.getName(), caps); - - // Select the appropriate provider - final SchemaSourceProvider delegate; - if (NetconfRemoteSchemaSourceProvider.isSupportedFor(caps)) { - delegate = new NetconfRemoteSchemaSourceProvider(device); - // FIXME caps do not contain urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring, since it is filtered out in getCapabilitites - } else if(session.getServerCapabilities().contains(NetconfRemoteSchemaSourceProvider.IETF_NETCONF_MONITORING.getNamespace().toString())) { - delegate = new NetconfRemoteSchemaSourceProvider(device); - } else { - LOG.info("Netconf server {} does not support IETF Netconf Monitoring", device.getName()); - delegate = SchemaSourceProviders.noopProvider(); - } - - device.bringUp(delegate, caps, isRollbackSupported(session.getServerCapabilities())); - - } - - private static boolean isRollbackSupported(final Collection serverCapabilities) { - // TODO rollback capability cannot be searched for in Set caps - // since this set does not contain module-less capabilities - return Sets.newHashSet(serverCapabilities).contains(NetconfMapping.NETCONF_ROLLBACK_ON_ERROR_URI.toString()); - } - - private synchronized void tearDown(final Exception e) { - session = null; - - /* - * Walk all requests, check if they have been executing - * or cancelled and remove them from the queue. - */ - final Iterator it = requests.iterator(); - while (it.hasNext()) { - final Request r = it.next(); - if (r.future.isUncancellable()) { - // FIXME: add a RpcResult instead? - r.future.setException(e); - it.remove(); - } else if (r.future.isCancelled()) { - // This just does some house-cleaning - it.remove(); - } - } - - device.bringDown(); - } - - @Override - public void onSessionDown(final NetconfClientSession session, final Exception e) { - LOG.debug("Session with {} went down", device.getName(), e); - tearDown(e); - } - - @Override - public void onSessionTerminated(final NetconfClientSession session, final NetconfTerminationReason reason) { - LOG.debug("Session with {} terminated {}", session, reason); - tearDown(new RuntimeException(reason.getErrorMessage())); - } - - @Override - public void onMessage(final NetconfClientSession session, final NetconfMessage message) { - /* - * Dispatch between notifications and messages. Messages need to be processed - * with lock held, notifications do not. - */ - if (isNotification(message)) { - processNotification(message); - } else { - processMessage(message); - } - } - - private synchronized void processMessage(final NetconfMessage message) { - final Request r = requests.peek(); - if (r.future.isUncancellable()) { - requests.poll(); - LOG.debug("Matched {} to {}", r.request, message); - - try { - NetconfMapping.checkValidReply(r.request, message); - } catch (IllegalStateException e) { - LOG.warn("Invalid request-reply match, reply message contains different message-id", e); - r.future.setException(e); - return; - } - - try { - NetconfMapping.checkSuccessReply(message); - } catch (NetconfDocumentedException | IllegalStateException e) { - LOG.warn("Error reply from remote device", e); - r.future.setException(e); - return; - } - - r.future.set(NetconfMapping.toRpcResult(message, r.rpc, device.getSchemaContext())); - } else { - LOG.warn("Ignoring unsolicited message", message); - } - } - - synchronized ListenableFuture> sendRequest(final NetconfMessage message, final QName rpc) { - if (session == null) { - LOG.debug("Session to {} is disconnected, failing RPC request {}", device.getName(), message); - return Futures.>immediateFuture(new RpcResult() { - @Override - public boolean isSuccessful() { - return false; - } - - @Override - public CompositeNode getResult() { - return null; - } - - @Override - public Collection getErrors() { - // FIXME: indicate that the session is down - return Collections.emptySet(); - } - }); - } - - final Request req = new Request(new UncancellableFuture>(true), message, rpc); - requests.add(req); - - session.sendMessage(req.request).addListener(new FutureListener() { - @Override - public void operationComplete(final Future future) throws Exception { - if (!future.isSuccess()) { - // We expect that a session down will occur at this point - LOG.debug("Failed to send request {}", XmlUtil.toString(req.request.getDocument()), future.cause()); - req.future.setException(future.cause()); - } else { - LOG.trace("Finished sending request {}", req.request); - } - } - }); - - return req.future; - } - - /** - * Process an incoming notification. - * - * @param notification Notification message - */ - private void processNotification(final NetconfMessage notification) { - this.device.logger.debug("Received NETCONF notification.", notification); - CompositeNode domNotification = NetconfMapping.toNotificationNode(notification, device.getSchemaContext()); - if (domNotification == null) { - return; - } - - MountProvisionInstance mountInstance = this.device.getMountInstance(); - if (mountInstance != null) { - mountInstance.publish(domNotification); - } - } - - private static boolean isNotification(final NetconfMessage message) { - final XmlElement xmle = XmlElement.fromDomDocument(message.getDocument()); - return XmlNetconfConstants.NOTIFICATION_ELEMENT_NAME.equals(xmle.getName()) ; - } -} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDeviceTwoPhaseCommitTransaction.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDeviceTwoPhaseCommitTransaction.java deleted file mode 100644 index 34cd9aa47b..0000000000 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDeviceTwoPhaseCommitTransaction.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (c) 2014 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.controller.sal.connect.netconf; - -import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_CANDIDATE_QNAME; -import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_COMMIT_QNAME; -import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_CONFIG_QNAME; -import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_EDIT_CONFIG_QNAME; -import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_ERROR_OPTION_QNAME; -import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_OPERATION_QNAME; -import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_RUNNING_QNAME; -import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_TARGET_QNAME; -import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.ROLLBACK_ON_ERROR_OPTION; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.ExecutionException; - -import org.opendaylight.controller.md.sal.common.api.data.DataCommitHandler.DataCommitTransaction; -import org.opendaylight.controller.md.sal.common.api.data.DataModification; -import org.opendaylight.yangtools.yang.common.QName; -import org.opendaylight.yangtools.yang.common.RpcError; -import org.opendaylight.yangtools.yang.common.RpcResult; -import org.opendaylight.yangtools.yang.data.api.CompositeNode; -import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier; -import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates; -import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument; -import org.opendaylight.yangtools.yang.data.api.Node; -import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode; -import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.base.Optional; -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; - -class NetconfDeviceTwoPhaseCommitTransaction implements DataCommitTransaction { - private static final Logger LOG = LoggerFactory.getLogger(NetconfDeviceTwoPhaseCommitTransaction.class); - private final DataModification modification; - private final NetconfDevice device; - private final boolean candidateSupported; - private final boolean rollbackSupported; - - public NetconfDeviceTwoPhaseCommitTransaction(final NetconfDevice device, - final DataModification modification, - final boolean candidateSupported, final boolean rollbackOnErrorSupported) { - this.device = Preconditions.checkNotNull(device); - this.modification = Preconditions.checkNotNull(modification); - this.candidateSupported = candidateSupported; - this.rollbackSupported = rollbackOnErrorSupported; - } - - void prepare() throws InterruptedException, ExecutionException { - for (InstanceIdentifier toRemove : modification.getRemovedConfigurationData()) { - sendDelete(toRemove); - } - for(Entry toUpdate : modification.getUpdatedConfigurationData().entrySet()) { - sendMerge(toUpdate.getKey(),toUpdate.getValue()); - } - } - - private void sendMerge(final InstanceIdentifier key, final CompositeNode value) throws InterruptedException, ExecutionException { - sendEditRpc(createEditStructure(key, Optional.absent(), Optional.of(value))); - } - - private void sendDelete(final InstanceIdentifier toDelete) throws InterruptedException, ExecutionException { - sendEditRpc(createEditStructure(toDelete, Optional.of("delete"), Optional. absent())); - } - - private void sendEditRpc(final CompositeNode editStructure) throws InterruptedException, ExecutionException { - CompositeNodeBuilder builder = configurationRpcBuilder(); - builder.setQName(NETCONF_EDIT_CONFIG_QNAME); - builder.add(editStructure); - - RpcResult rpcResult = device.invokeRpc(NETCONF_EDIT_CONFIG_QNAME, builder.toInstance()).get(); - Preconditions.checkState(rpcResult.isSuccessful(),"Rpc Result was unsuccessful"); - } - - private CompositeNodeBuilder configurationRpcBuilder() { - CompositeNodeBuilder ret = ImmutableCompositeNode.builder(); - - Node targetNode; - if(candidateSupported) { - targetNode = ImmutableCompositeNode.create(NETCONF_CANDIDATE_QNAME, ImmutableList.>of()); - } else { - targetNode = ImmutableCompositeNode.create(NETCONF_RUNNING_QNAME, ImmutableList.>of()); - } - - Node targetWrapperNode = ImmutableCompositeNode.create(NETCONF_TARGET_QNAME, ImmutableList.>of(targetNode)); - - if(rollbackSupported) { - LOG.debug("Rollback-on-error supported, setting {} to {}", NETCONF_ERROR_OPTION_QNAME, ROLLBACK_ON_ERROR_OPTION); - ret.addLeaf(NETCONF_ERROR_OPTION_QNAME, ROLLBACK_ON_ERROR_OPTION); - } - - ret.add(targetWrapperNode); - return ret; - } - - private CompositeNode createEditStructure(final InstanceIdentifier dataPath, final Optional operation, - final Optional lastChildOverride) { - List path = dataPath.getPath(); - List reversed = Lists.reverse(path); - CompositeNode previous = null; - boolean isLast = true; - for (PathArgument arg : reversed) { - CompositeNodeBuilder builder = ImmutableCompositeNode.builder(); - builder.setQName(arg.getNodeType()); - Map predicates = Collections.emptyMap(); - if (arg instanceof NodeIdentifierWithPredicates) { - predicates = ((NodeIdentifierWithPredicates) arg).getKeyValues(); - } - for (Entry entry : predicates.entrySet()) { - builder.addLeaf(entry.getKey(), entry.getValue()); - } - - if (isLast) { - if (operation.isPresent()) { - builder.setAttribute(NETCONF_OPERATION_QNAME, operation.get()); - } - if (lastChildOverride.isPresent()) { - List> children = lastChildOverride.get().getValue(); - for(Node child : children) { - if(!predicates.containsKey(child.getKey())) { - builder.add(child); - } - } - - } - } else { - builder.add(previous); - } - previous = builder.toInstance(); - isLast = false; - } - return ImmutableCompositeNode.create(NETCONF_CONFIG_QNAME, ImmutableList.>of(previous)); - } - - @Override - public RpcResult finish() { - CompositeNodeBuilder commitInput = ImmutableCompositeNode.builder(); - commitInput.setQName(NETCONF_COMMIT_QNAME); - try { - final RpcResult rpcResult = device.invokeRpc(NetconfMapping.NETCONF_COMMIT_QNAME, commitInput.toInstance()).get(); - return new RpcResult() { - - @Override - public boolean isSuccessful() { - return rpcResult.isSuccessful(); - } - - @Override - public Void getResult() { - return null; - } - - @Override - public Collection getErrors() { - return rpcResult.getErrors(); - } - }; - } catch (final InterruptedException | ExecutionException e) { - LOG.warn("Failed to finish operation", e); - return new RpcResult() { - @Override - public boolean isSuccessful() { - return false; - } - - @Override - public Void getResult() { - return null; - } - - @Override - public Collection getErrors() { - // FIXME: wrap the exception - return Collections.emptySet(); - } - }; - } - } - - @Override - public DataModification getModification() { - return this.modification; - } - - @Override - public RpcResult rollback() throws IllegalStateException { - // TODO Auto-generated method stub - return null; - } -} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfInventoryUtils.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfInventoryUtils.java deleted file mode 100644 index b68f18f52e..0000000000 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfInventoryUtils.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2014 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.controller.sal.connect.netconf; - -import org.opendaylight.yangtools.yang.common.QName; -import org.opendaylight.yangtools.yang.data.api.CompositeNode; - -public class NetconfInventoryUtils { - public static final QName NETCONF_MOUNT = null; - public static final QName NETCONF_ENDPOINT = null; - public static final QName NETCONF_ENDPOINT_ADDRESS = null; - public static final QName NETCONF_ENDPOINT_PORT = null; - - private NetconfInventoryUtils() { - throw new UnsupportedOperationException("Utility class cannot be instantiated"); - } - - public static String getEndpointAddress(CompositeNode node) { - return node.getCompositesByName(NETCONF_ENDPOINT).get(0).getFirstSimpleByName(NETCONF_ENDPOINT_ADDRESS).getValue().toString(); - } - - public static String getEndpointPort(CompositeNode node) { - return node.getCompositesByName(NETCONF_ENDPOINT).get(0).getFirstSimpleByName(NETCONF_ENDPOINT_PORT).getValue().toString(); - } -} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfRemoteSchemaSourceProvider.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfRemoteSchemaSourceProvider.java deleted file mode 100644 index 31c6bd0138..0000000000 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfRemoteSchemaSourceProvider.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2014 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.controller.sal.connect.netconf; - -import java.util.Collection; -import java.util.concurrent.ExecutionException; - -import org.opendaylight.yangtools.yang.common.QName; -import org.opendaylight.yangtools.yang.common.RpcResult; -import org.opendaylight.yangtools.yang.data.api.CompositeNode; -import org.opendaylight.yangtools.yang.data.api.SimpleNode; -import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode; -import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder; -import org.opendaylight.yangtools.yang.model.util.repo.SchemaSourceProvider; - -import com.google.common.base.Optional; -import com.google.common.base.Preconditions; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -class NetconfRemoteSchemaSourceProvider implements SchemaSourceProvider { - - public static final QName IETF_NETCONF_MONITORING = QName.create( - "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring", "2010-10-04", "ietf-netconf-monitoring"); - public static final QName GET_SCHEMA_QNAME = QName.create(IETF_NETCONF_MONITORING, "get-schema"); - public static final QName GET_DATA_QNAME = QName.create(IETF_NETCONF_MONITORING, "data"); - - private final NetconfDevice device; - - private final Logger logger; - - public NetconfRemoteSchemaSourceProvider(NetconfDevice device) { - this.device = Preconditions.checkNotNull(device); - logger = LoggerFactory.getLogger(NetconfDevice.class + "#" + device.getName()); - } - - @Override - public Optional getSchemaSource(String moduleName, Optional revision) { - CompositeNodeBuilder request = ImmutableCompositeNode.builder(); // - request.setQName(GET_SCHEMA_QNAME) // - .addLeaf("format", "yang") // - .addLeaf("identifier", moduleName); // - if (revision.isPresent()) { - request.addLeaf("version", revision.get()); - } - - logger.trace("Loading YANG schema source for {}:{}", moduleName, revision); - try { - RpcResult schemaReply = device.invokeRpc(GET_SCHEMA_QNAME, request.toInstance()).get(); - if (schemaReply.isSuccessful()) { - String schemaBody = getSchemaFromRpc(schemaReply.getResult()); - if (schemaBody != null) { - device.logger.trace("YANG Schema successfully retrieved from remote for {}:{}", moduleName, revision); - return Optional.of(schemaBody); - } - } - logger.warn("YANG shcema was not successfully retrieved. Errors: {}", schemaReply.getErrors()); - } catch (InterruptedException | ExecutionException e) { - logger.warn("YANG shcema was not successfully retrieved.", e); - } - return Optional.absent(); - } - - private String getSchemaFromRpc(CompositeNode result) { - if (result == null) { - return null; - } - SimpleNode simpleNode = result.getFirstSimpleByName(GET_DATA_QNAME.withoutRevision()); - Object potential = simpleNode.getValue(); - if (potential instanceof String) { - return (String) potential; - } - return null; - } - - public static final boolean isSupportedFor(Collection capabilities) { - return capabilities.contains(IETF_NETCONF_MONITORING); - } -} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCommunicator.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCommunicator.java new file mode 100644 index 0000000000..fa6e252293 --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCommunicator.java @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2014 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.controller.sal.connect.netconf.listener; + +import java.util.ArrayDeque; +import java.util.Collections; +import java.util.Iterator; +import java.util.Queue; + +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.api.NetconfTerminationReason; +import org.opendaylight.controller.netconf.client.NetconfClientDispatcher; +import org.opendaylight.controller.netconf.client.NetconfClientSession; +import org.opendaylight.controller.netconf.client.NetconfClientSessionListener; +import org.opendaylight.controller.netconf.client.conf.NetconfClientConfiguration; +import org.opendaylight.controller.netconf.client.conf.NetconfReconnectingClientConfiguration; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.opendaylight.controller.sal.common.util.RpcErrors; +import org.opendaylight.controller.sal.common.util.Rpcs; +import org.opendaylight.controller.sal.connect.api.RemoteDevice; +import org.opendaylight.controller.sal.connect.api.RemoteDeviceCommunicator; +import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil; +import org.opendaylight.controller.sal.connect.util.FailedRpcResult; +import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.RpcError; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; + +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.FutureListener; + +public class NetconfDeviceCommunicator implements NetconfClientSessionListener, RemoteDeviceCommunicator { + + private static final Logger logger = LoggerFactory.getLogger(NetconfDeviceCommunicator.class); + + private static final RpcResult FAILED_RPC_RESULT = new FailedRpcResult<>(RpcErrors.getRpcError( + null, null, null, RpcError.ErrorSeverity.ERROR, "Netconf session disconnected", + RpcError.ErrorType.PROTOCOL, null)); + + private final RemoteDevice remoteDevice; + private final RemoteDeviceId id; + + public NetconfDeviceCommunicator(final RemoteDeviceId id, + final RemoteDevice remoteDevice) { + this.id = id; + this.remoteDevice = remoteDevice; + } + + private final Queue requests = new ArrayDeque<>(); + private NetconfClientSession session; + + @Override + public synchronized void onSessionUp(final NetconfClientSession session) { + logger.debug("{}: Session established", id); + this.session = session; + + final NetconfSessionCapabilities netconfSessionCapabilities = NetconfSessionCapabilities.fromNetconfSession(session); + logger.trace("{}: Session advertised capabilities: {}", id, netconfSessionCapabilities); + + remoteDevice.onRemoteSessionUp(netconfSessionCapabilities, this); + } + + public void initializeRemoteConnection(final NetconfClientDispatcher dispatch, + final NetconfReconnectingClientConfiguration config) { + dispatch.createReconnectingClient(config); + } + + private synchronized void tearDown(final Exception e) { + remoteDevice.onRemoteSessionDown(); + session = null; + + /* + * Walk all requests, check if they have been executing + * or cancelled and remove them from the queue. + */ + final Iterator it = requests.iterator(); + while (it.hasNext()) { + final Request r = it.next(); + if (r.future.isUncancellable()) { + r.future.setException(e); + it.remove(); + } else if (r.future.isCancelled()) { + // This just does some house-cleaning + it.remove(); + } + } + } + + @Override + public void onSessionDown(final NetconfClientSession session, final Exception e) { + logger.warn("{}: Session went down", id, e); + tearDown(e); + } + + @Override + public void onSessionTerminated(final NetconfClientSession session, final NetconfTerminationReason reason) { + logger.warn("{}: Session terminated {}", id, reason); + tearDown(new RuntimeException(reason.getErrorMessage())); + } + + @Override + public void onMessage(final NetconfClientSession session, final NetconfMessage message) { + /* + * Dispatch between notifications and messages. Messages need to be processed + * with lock held, notifications do not. + */ + if (isNotification(message)) { + processNotification(message); + } else { + processMessage(message); + } + } + + private synchronized void processMessage(final NetconfMessage message) { + final Request r = requests.peek(); + if (r.future.isUncancellable()) { + requests.poll(); + + logger.debug("{}: Message received {}", id, message); + + if(logger.isTraceEnabled()) { + logger.trace("{}: Matched request: {} to response: {}", id, msgToS(r.request), msgToS(message)); + } + + try { + NetconfMessageTransformUtil.checkValidReply(r.request, message); + } catch (final IllegalStateException e) { + logger.warn("{}: Invalid request-reply match, reply message contains different message-id, request: {}, response: {}", id, + msgToS(r.request), msgToS(message), e); + r.future.setException(e); + return; + } + + try { + NetconfMessageTransformUtil.checkSuccessReply(message); + } catch (NetconfDocumentedException | IllegalStateException e) { + logger.warn("{}: Error reply from remote device, request: {}, response: {}", id, + msgToS(r.request), msgToS(message), e); + r.future.setException(e); + return; + } + + r.future.set(Rpcs.getRpcResult(true, message, Collections.emptySet())); + } else { + logger.warn("{}: Ignoring unsolicited message {}", id, msgToS(message)); + } + } + + @Override + public void close() { + tearDown(new RuntimeException("Closed")); + } + + private static String msgToS(final NetconfMessage msg) { + return XmlUtil.toString(msg.getDocument()); + } + + @Override + public synchronized ListenableFuture> sendRequest(final NetconfMessage message, final QName rpc) { + if(logger.isTraceEnabled()) { + logger.trace("{}: Sending message {}", id, msgToS(message)); + } + + if (session == null) { + logger.warn("{}: Session is disconnected, failing RPC request {}", id, message); + return Futures.immediateFuture(FAILED_RPC_RESULT); + } + + final Request req = new Request(new UncancellableFuture>(true), message, rpc); + requests.add(req); + + session.sendMessage(req.request).addListener(new FutureListener() { + @Override + public void operationComplete(final Future future) throws Exception { + if (!future.isSuccess()) { + // We expect that a session down will occur at this point + logger.debug("{}: Failed to send request {}", id, XmlUtil.toString(req.request.getDocument()), future.cause()); + req.future.setException(future.cause()); + } else { + logger.trace("{}: Finished sending request {}", id, req.request); + } + } + }); + + return req.future; + } + + private void processNotification(final NetconfMessage notification) { + logger.debug("{}: Notification received: {}", id, notification); + + if(logger.isTraceEnabled()) { + logger.trace("{}: Notification received: {}", id, msgToS(notification)); + } + + remoteDevice.onNotification(notification); + } + + private static boolean isNotification(final NetconfMessage message) { + final XmlElement xmle = XmlElement.fromDomDocument(message.getDocument()); + return XmlNetconfConstants.NOTIFICATION_ELEMENT_NAME.equals(xmle.getName()) ; + } + + private static final class Request { + final UncancellableFuture> future; + final NetconfMessage request; + final QName rpc; + + private Request(final UncancellableFuture> future, final NetconfMessage request, final QName rpc) { + this.future = future; + this.request = request; + this.rpc = rpc; + } + } +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilities.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilities.java new file mode 100644 index 0000000000..82903ea4ec --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilities.java @@ -0,0 +1,111 @@ +package org.opendaylight.controller.sal.connect.netconf.listener; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Set; + +import org.opendaylight.controller.netconf.client.NetconfClientSession; +import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil; +import org.opendaylight.yangtools.yang.common.QName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Objects; +import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; + +public final class NetconfSessionCapabilities { + + private static final Logger logger = LoggerFactory.getLogger(NetconfSessionCapabilities.class); + + private final Set capabilities; + + private final Set moduleBasedCaps; + + private NetconfSessionCapabilities(final Set capabilities, final Set moduleBasedCaps) { + this.capabilities = capabilities; + this.moduleBasedCaps = moduleBasedCaps; + } + + public Set getModuleBasedCaps() { + return moduleBasedCaps; + } + + public boolean containsCapability(final String capability) { + return capabilities.contains(capability); + } + + public boolean containsCapability(final QName capability) { + return moduleBasedCaps.contains(capability); + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("capabilities", capabilities) + .add("rollback", isRollbackSupported()) + .add("monitoring", isMonitoringSupported()) + .toString(); + } + + public boolean isRollbackSupported() { + return containsCapability(NetconfMessageTransformUtil.NETCONF_ROLLBACK_ON_ERROR_URI.toString()); + } + + public boolean isMonitoringSupported() { + return containsCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING) + || containsCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING.getNamespace().toString()); + } + + public static NetconfSessionCapabilities fromNetconfSession(final NetconfClientSession session) { + return fromStrings(session.getServerCapabilities()); + } + + public static NetconfSessionCapabilities fromStrings(final Collection capabilities) { + final Set moduleBasedCaps = Sets.newHashSet(); + + for (final String capability : capabilities) { + if(isModuleBasedCapability(capability)) { + final String[] parts = capability.split("\\?"); + final String namespace = parts[0]; + final FluentIterable queryParams = FluentIterable.from(Arrays.asList(parts[1].split("&"))); + + String revision = getStringAndTransform(queryParams, "revision=", "revision="); + + final String moduleName = getStringAndTransform(queryParams, "module=", "module="); + + if (revision == null) { + logger.debug("Netconf device was not reporting revision correctly, trying to get amp;revision="); + revision = getStringAndTransform(queryParams, "amp;revision=", "amp;revision="); + + if (revision == null) { + logger.warn("Netconf device returned revision incorrectly escaped for {}", capability); + } + } + moduleBasedCaps.add(QName.create(namespace, revision, moduleName)); + } + } + + return new NetconfSessionCapabilities(Sets.newHashSet(capabilities), moduleBasedCaps); + } + + private static boolean isModuleBasedCapability(final String capability) { + return capability.contains("?") && capability.contains("module=") && capability.contains("revision="); + } + + private static String getStringAndTransform(final Iterable queryParams, final String match, + final String substringToRemove) { + final Optional found = Iterables.tryFind(queryParams, new Predicate() { + @Override + public boolean apply(final String input) { + return input.startsWith(match); + } + }); + + return found.isPresent() ? found.get().replaceAll(substringToRemove, "") : null; + } + +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/UncancellableFuture.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/UncancellableFuture.java similarity index 67% rename from opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/UncancellableFuture.java rename to opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/UncancellableFuture.java index c353f86eb6..8d89ff8b93 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/UncancellableFuture.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/UncancellableFuture.java @@ -5,7 +5,7 @@ * 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.controller.sal.connect.netconf; +package org.opendaylight.controller.sal.connect.netconf.listener; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; @@ -17,7 +17,7 @@ final class UncancellableFuture extends AbstractFuture { @GuardedBy("this") private boolean uncancellable = false; - public UncancellableFuture(boolean uncancellable) { + public UncancellableFuture(final boolean uncancellable) { this.uncancellable = uncancellable; } @@ -35,23 +35,19 @@ final class UncancellableFuture extends AbstractFuture { } @Override - public synchronized boolean cancel(boolean mayInterruptIfRunning) { - if (uncancellable) { - return false; - } - - return super.cancel(mayInterruptIfRunning); + public synchronized boolean cancel(final boolean mayInterruptIfRunning) { + return uncancellable ? false : super.cancel(mayInterruptIfRunning); } @Override - public synchronized boolean set(@Nullable V value) { - Preconditions.checkState(uncancellable == true); + public synchronized boolean set(@Nullable final V value) { + Preconditions.checkState(uncancellable); return super.set(value); } @Override - protected boolean setException(Throwable throwable) { - Preconditions.checkState(uncancellable == true); + protected boolean setException(final Throwable throwable) { + Preconditions.checkState(uncancellable); return super.setException(throwable); } } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/package-info.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/package-info.java new file mode 100644 index 0000000000..4a95582657 --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/package-info.java @@ -0,0 +1,4 @@ +/** + * Implementation of netconf southbound connector + */ +package org.opendaylight.controller.sal.connect.netconf; diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceCommitHandler.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceCommitHandler.java new file mode 100644 index 0000000000..457b8c36b9 --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceCommitHandler.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2014 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.controller.sal.connect.netconf.sal; + +import java.util.concurrent.ExecutionException; + +import org.opendaylight.controller.md.sal.common.api.data.DataCommitHandler; +import org.opendaylight.controller.md.sal.common.api.data.DataModification; +import org.opendaylight.controller.sal.common.util.RpcErrors; +import org.opendaylight.controller.sal.connect.util.FailedRpcResult; +import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; +import org.opendaylight.controller.sal.core.api.RpcImplementation; +import org.opendaylight.yangtools.yang.common.RpcError; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class NetconfDeviceCommitHandler implements DataCommitHandler { + + private static final Logger logger= LoggerFactory.getLogger(NetconfDeviceCommitHandler.class); + + private final RemoteDeviceId id; + private final RpcImplementation rpc; + private final boolean rollbackSupported; + + public NetconfDeviceCommitHandler(final RemoteDeviceId id, final RpcImplementation rpc, final boolean rollbackSupported) { + this.id = id; + this.rpc = rpc; + this.rollbackSupported = rollbackSupported; + } + + @Override + public DataCommitTransaction requestCommit( + final DataModification modification) { + + final NetconfDeviceTwoPhaseCommitTransaction twoPhaseCommit = new NetconfDeviceTwoPhaseCommitTransaction(id, rpc, + modification, true, rollbackSupported); + try { + twoPhaseCommit.prepare(); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(id + ": Interrupted while waiting for response", e); + } catch (final ExecutionException e) { + logger.warn("%s: Error executing pre commit operation on remote device", id, e); + return new FailingTransaction(twoPhaseCommit, e); + } + + return twoPhaseCommit; + } + + /** + * Always fail commit transaction that rolls back delegate transaction afterwards + */ + private class FailingTransaction implements DataCommitTransaction { + private final NetconfDeviceTwoPhaseCommitTransaction twoPhaseCommit; + private final ExecutionException e; + + public FailingTransaction(final NetconfDeviceTwoPhaseCommitTransaction twoPhaseCommit, final ExecutionException e) { + this.twoPhaseCommit = twoPhaseCommit; + this.e = e; + } + + @Override + public DataModification getModification() { + return twoPhaseCommit.getModification(); + } + + @Override + public RpcResult finish() throws IllegalStateException { + return new FailedRpcResult<>(RpcErrors.getRpcError(null, null, null, RpcError.ErrorSeverity.ERROR, + id + ": Unexpected operation error during pre-commit operations", RpcError.ErrorType.APPLICATION, e)); + } + + @Override + public RpcResult rollback() throws IllegalStateException { + return twoPhaseCommit.rollback(); + } + } +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDataReader.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDataReader.java new file mode 100644 index 0000000000..2909baccdb --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDataReader.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2014 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.controller.sal.connect.netconf.sal; + +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.CONFIG_SOURCE_RUNNING; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_DATA_QNAME; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_GET_CONFIG_QNAME; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_GET_QNAME; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.toFilterStructure; + +import java.util.concurrent.ExecutionException; + +import org.opendaylight.controller.md.sal.common.api.data.DataReader; +import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil; +import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; +import org.opendaylight.controller.sal.core.api.RpcImplementation; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.data.api.SimpleNode; + +public final class NetconfDeviceDataReader implements DataReader { + + private final RpcImplementation rpc; + private final RemoteDeviceId id; + + public NetconfDeviceDataReader(final RemoteDeviceId id, final RpcImplementation rpc) { + this.id = id; + this.rpc = rpc; + } + + @Override + public CompositeNode readConfigurationData(final InstanceIdentifier path) { + final RpcResult result; + try { + result = rpc.invokeRpc(NETCONF_GET_CONFIG_QNAME, + NetconfMessageTransformUtil.wrap(NETCONF_GET_CONFIG_QNAME, CONFIG_SOURCE_RUNNING, toFilterStructure(path))).get(); + } catch (final InterruptedException e) { + throw onInterruptedException(e); + } catch (final ExecutionException e) { + throw new RuntimeException(id + ": Read configuration data " + path + " failed", e); + } + + final CompositeNode data = result.getResult().getFirstCompositeByName(NETCONF_DATA_QNAME); + return data == null ? null : (CompositeNode) findNode(data, path); + } + + private RuntimeException onInterruptedException(final InterruptedException e) { + Thread.currentThread().interrupt(); + return new RuntimeException(id + ": Interrupted while waiting for response", e); + } + + @Override + public CompositeNode readOperationalData(final InstanceIdentifier path) { + final RpcResult result; + try { + result = rpc.invokeRpc(NETCONF_GET_QNAME, NetconfMessageTransformUtil.wrap(NETCONF_GET_QNAME, toFilterStructure(path))).get(); + } catch (final InterruptedException e) { + throw onInterruptedException(e); + } catch (final ExecutionException e) { + throw new RuntimeException(id + ": Read operational data " + path + " failed", e); + } + + final CompositeNode data = result.getResult().getFirstCompositeByName(NETCONF_DATA_QNAME); + return (CompositeNode) findNode(data, path); + } + + private static Node findNode(final CompositeNode node, final InstanceIdentifier identifier) { + + Node current = node; + for (final InstanceIdentifier.PathArgument arg : identifier.getPath()) { + if (current instanceof SimpleNode) { + return null; + } else if (current instanceof CompositeNode) { + final CompositeNode currentComposite = (CompositeNode) current; + + current = currentComposite.getFirstCompositeByName(arg.getNodeType()); + if (current == null) { + current = currentComposite.getFirstCompositeByName(arg.getNodeType().withoutRevision()); + } + if (current == null) { + current = currentComposite.getFirstSimpleByName(arg.getNodeType()); + } + if (current == null) { + current = currentComposite.getFirstSimpleByName(arg.getNodeType().withoutRevision()); + } + if (current == null) { + return null; + } + } + } + return current; + } +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDatastoreAdapter.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDatastoreAdapter.java new file mode 100644 index 0000000000..e491496eed --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDatastoreAdapter.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2014 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.controller.sal.connect.netconf.sal; + +import com.google.common.base.Function; +import com.google.common.base.Preconditions; +import com.google.common.collect.FluentIterable; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import org.opendaylight.controller.md.sal.common.api.TransactionStatus; +import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction; +import org.opendaylight.controller.sal.binding.api.data.DataProviderService; +import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.inventory.rev140108.NetconfNode; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.inventory.rev140108.NetconfNodeBuilder; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Asynchronous (Binding-aware) adapter over datastore subtree for netconf device. + * + * All data changes are submitted to an ExecutorService to avoid Thread blocking while sal is waiting for schema. + */ +final class NetconfDeviceDatastoreAdapter implements AutoCloseable { + + private static final Logger logger = LoggerFactory.getLogger(NetconfDeviceDatastoreAdapter.class); + + private final RemoteDeviceId id; + private final DataProviderService dataService; + private final ListeningExecutorService executor; + + NetconfDeviceDatastoreAdapter(final RemoteDeviceId deviceId, final DataProviderService dataService, + final ExecutorService executor) { + this.id = Preconditions.checkNotNull(deviceId); + this.dataService = Preconditions.checkNotNull(dataService); + this.executor = MoreExecutors.listeningDecorator(Preconditions.checkNotNull(executor)); + + // Initial data change scheduled + submitDataChangeToExecutor(this.executor, new Runnable() { + @Override + public void run() { + initDeviceData(); + } + }, deviceId); + } + + public void updateDeviceState(final boolean up, final Set capabilities) { + submitDataChangeToExecutor(this.executor, new Runnable() { + @Override + public void run() { + updateDeviceStateInternal(up, capabilities); + } + }, id); + } + + private void updateDeviceStateInternal(final boolean up, final Set capabilities) { + final org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node data = buildDataForDeviceState( + up, capabilities, id); + + final DataModificationTransaction transaction = dataService.beginTransaction(); + logger.trace("{}: Update device state transaction {} putting operational data started.", id, transaction.getIdentifier()); + transaction.removeOperationalData(id.getBindingPath()); + transaction.putOperationalData(id.getBindingPath(), data); + logger.trace("{}: Update device state transaction {} putting operational data ended.", id, transaction.getIdentifier()); + + commitTransaction(transaction, "update"); + } + + private void removeDeviceConfigAndState() { + final DataModificationTransaction transaction = dataService.beginTransaction(); + logger.trace("{}: Close device state transaction {} removing all data started.", id, transaction.getIdentifier()); + transaction.removeConfigurationData(id.getBindingPath()); + transaction.removeOperationalData(id.getBindingPath()); + logger.trace("{}: Close device state transaction {} removing all data ended.", id, transaction.getIdentifier()); + + commitTransaction(transaction, "close"); + } + + private void initDeviceData() { + final DataModificationTransaction transaction = dataService.beginTransaction(); + + final InstanceIdentifier path = id.getBindingPath(); + + final Node nodeWithId = getNodeWithId(id); + if (operationalNodeNotExisting(transaction, path)) { + transaction.putOperationalData(path, nodeWithId); + } + if (configurationNodeNotExisting(transaction, path)) { + transaction.putConfigurationData(path, nodeWithId); + } + + commitTransaction(transaction, "init"); + } + + private void commitTransaction(final DataModificationTransaction transaction, final String txType) { + // attempt commit + final RpcResult result; + try { + result = transaction.commit().get(); + } catch (InterruptedException | ExecutionException e) { + logger.error("{}: Transaction({}) failed", id, txType, e); + throw new IllegalStateException(id + " Transaction(" + txType + ") not committed correctly", e); + } + + // verify success result + committed state + if (isUpdateSuccessful(result)) { + logger.trace("{}: Transaction({}) {} SUCCESSFUL", id, txType, transaction.getIdentifier()); + } else { + logger.error("{}: Transaction({}) {} FAILED!", id, txType, transaction.getIdentifier()); + throw new IllegalStateException(id + " Transaction(" + txType + ") not committed correctly, " + + "Errors: " + result.getErrors()); + } + } + + @Override + public void close() throws Exception { + // Remove device data from datastore + submitDataChangeToExecutor(executor, new Runnable() { + @Override + public void run() { + removeDeviceConfigAndState(); + } + }, id); + } + + private static boolean isUpdateSuccessful(final RpcResult result) { + return result.getResult() == TransactionStatus.COMMITED && result.isSuccessful(); + } + + private static void submitDataChangeToExecutor(final ListeningExecutorService executor, final Runnable r, + final RemoteDeviceId id) { + // Submit data change + final ListenableFuture f = executor.submit(r); + // Verify update execution + Futures.addCallback(f, new FutureCallback() { + @Override + public void onSuccess(final Object result) { + logger.debug("{}: Device data updated successfully", id); + } + + @Override + public void onFailure(final Throwable t) { + logger.warn("{}: Device data update failed", id, t); + } + }); + } + + public static org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node buildDataForDeviceState( + final boolean up, final Set capabilities, final RemoteDeviceId id) { + + final NodeBuilder nodeBuilder = getNodeWithIdBuilder(id); + final NetconfNodeBuilder netconfNodeBuilder = new NetconfNodeBuilder(); + netconfNodeBuilder.setConnected(up); + netconfNodeBuilder.setInitialCapability(FluentIterable.from(capabilities) + .transform(new Function() { + @Override + public String apply(final QName input) { + return input.toString(); + } + }).toList()); + nodeBuilder.addAugmentation(NetconfNode.class, netconfNodeBuilder.build()); + + return nodeBuilder.build(); + } + + private static boolean configurationNodeNotExisting(final DataModificationTransaction transaction, + final InstanceIdentifier path) { + return null == transaction.readConfigurationData(path); + } + + private static boolean operationalNodeNotExisting(final DataModificationTransaction transaction, + final InstanceIdentifier path) { + return null == transaction.readOperationalData(path); + } + + private static Node getNodeWithId(final RemoteDeviceId id) { + final NodeBuilder nodeBuilder = getNodeWithIdBuilder(id); + return nodeBuilder.build(); + } + + private static NodeBuilder getNodeWithIdBuilder(final RemoteDeviceId id) { + final NodeBuilder nodeBuilder = new NodeBuilder(); + nodeBuilder.setKey(id.getBindingKey()); + nodeBuilder.setId(id.getBindingKey().getId()); + return nodeBuilder; + } +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceRpc.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceRpc.java new file mode 100644 index 0000000000..927d41861a --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceRpc.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2014 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.controller.sal.connect.netconf.sal; + +import com.google.common.base.Function; +import com.google.common.util.concurrent.Futures; +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import javax.annotation.Nullable; +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.sal.common.util.Rpcs; +import org.opendaylight.controller.sal.connect.api.MessageTransformer; +import org.opendaylight.controller.sal.connect.api.RemoteDeviceCommunicator; +import org.opendaylight.controller.sal.core.api.RpcImplementation; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; + +import com.google.common.util.concurrent.ListenableFuture; + +/** + * Invokes RPC by sending netconf message via listener. Also transforms result from NetconfMessage to CompositeNode. + */ +public final class NetconfDeviceRpc implements RpcImplementation { + private final RemoteDeviceCommunicator listener; + private final MessageTransformer transformer; + + public NetconfDeviceRpc(final RemoteDeviceCommunicator listener, final MessageTransformer transformer) { + this.listener = listener; + this.transformer = transformer; + } + + @Override + public Set getSupportedRpcs() { + // TODO is this correct ? + return Collections.emptySet(); + } + + @Override + public ListenableFuture> invokeRpc(final QName rpc, final CompositeNode input) { + final NetconfMessage message = transformRequest(rpc, input); + final ListenableFuture> delegateFutureWithPureResult = listener.sendRequest( + message, rpc); + + + return Futures.transform(delegateFutureWithPureResult, new Function, RpcResult>() { + @Override + public RpcResult apply(@Nullable final RpcResult input) { + return transformResult(input, rpc); + } + }); + } + + private NetconfMessage transformRequest(final QName rpc, final CompositeNode input) { + return transformer.toRpcRequest(rpc, input); + } + + private RpcResult transformResult(final RpcResult netconfMessageRpcResult, + final QName rpc) { + if (netconfMessageRpcResult.isSuccessful()) { + return transformer.toRpcResult(netconfMessageRpcResult.getResult(), rpc); + } else { + return Rpcs.getRpcResult(false, netconfMessageRpcResult.getErrors()); + } + } + +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceSalFacade.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceSalFacade.java new file mode 100644 index 0000000000..37b87045d5 --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceSalFacade.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2014 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.controller.sal.connect.netconf.sal; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import org.opendaylight.controller.sal.binding.api.BindingAwareBroker; +import org.opendaylight.controller.sal.connect.api.RemoteDeviceHandler; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities; +import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; +import org.opendaylight.controller.sal.core.api.Broker; +import org.opendaylight.controller.sal.core.api.RpcImplementation; +import org.opendaylight.controller.sal.core.api.mount.MountProvisionInstance; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier; +import org.opendaylight.yangtools.yang.model.api.RpcDefinition; +import org.opendaylight.yangtools.yang.model.api.SchemaContextProvider; +import org.osgi.framework.BundleContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class NetconfDeviceSalFacade implements AutoCloseable, RemoteDeviceHandler { + + private static final Logger logger= LoggerFactory.getLogger(NetconfDeviceSalFacade.class); + private static final InstanceIdentifier ROOT_PATH = InstanceIdentifier.builder().toInstance(); + + private final RemoteDeviceId id; + private final NetconfDeviceSalProvider salProvider; + + private final List salRegistrations = Lists.newArrayList(); + + public NetconfDeviceSalFacade(final RemoteDeviceId id, final Broker domBroker, final BindingAwareBroker bindingBroker, final BundleContext bundleContext, final ExecutorService executor) { + this.id = id; + this.salProvider = new NetconfDeviceSalProvider(id, executor); + registerToSal(domBroker, bindingBroker, bundleContext); + } + + public void registerToSal(final Broker domRegistryDependency, final BindingAwareBroker bindingBroker, final BundleContext bundleContext) { + domRegistryDependency.registerProvider(salProvider, bundleContext); + bindingBroker.registerProvider(salProvider, bundleContext); + } + + @Override + public synchronized void onNotification(final CompositeNode domNotification) { + salProvider.getMountInstance().publish(domNotification); + } + + @Override + public synchronized void onDeviceConnected(final SchemaContextProvider remoteSchemaContextProvider, + final NetconfSessionCapabilities netconfSessionPreferences, final RpcImplementation deviceRpc) { + salProvider.getMountInstance().setSchemaContext(remoteSchemaContextProvider.getSchemaContext()); + salProvider.getDatastoreAdapter().updateDeviceState(true, netconfSessionPreferences.getModuleBasedCaps()); + registerDataHandlersToSal(deviceRpc, netconfSessionPreferences); + registerRpcsToSal(deviceRpc); + } + + @Override + public void onDeviceDisconnected() { + salProvider.getDatastoreAdapter().updateDeviceState(false, Collections.emptySet()); + } + + private void registerRpcsToSal(final RpcImplementation deviceRpc) { + final MountProvisionInstance mountInstance = salProvider.getMountInstance(); + + final Map failedRpcs = Maps.newHashMap(); + for (final RpcDefinition rpcDef : mountInstance.getSchemaContext().getOperations()) { + try { + salRegistrations.add(mountInstance.addRpcImplementation(rpcDef.getQName(), deviceRpc)); + logger.debug("{}: Rpc {} from netconf registered successfully", id, rpcDef.getQName()); + } catch (final Exception e) { + // Only debug per rpc, warn for all of them at the end to pollute log a little less (e.g. routed rpcs) + logger.debug("{}: Unable to register rpc {} from netconf device. This rpc will not be available", id, + rpcDef.getQName(), e); + failedRpcs.put(rpcDef.getQName(), e.getClass() + ":" + e.getMessage()); + } + } + + if (failedRpcs.isEmpty() == false) { + if (logger.isDebugEnabled()) { + logger.warn("{}: Some rpcs from netconf device were not registered: {}", id, failedRpcs); + } else { + logger.warn("{}: Some rpcs from netconf device were not registered: {}", id, failedRpcs.keySet()); + } + } + } + + private void registerDataHandlersToSal(final RpcImplementation deviceRpc, + final NetconfSessionCapabilities netconfSessionPreferences) { + final NetconfDeviceDataReader dataReader = new NetconfDeviceDataReader(id, deviceRpc); + final NetconfDeviceCommitHandler commitHandler = new NetconfDeviceCommitHandler(id, deviceRpc, + netconfSessionPreferences.isRollbackSupported()); + + final MountProvisionInstance mountInstance = salProvider.getMountInstance(); + salRegistrations.add(mountInstance.registerConfigurationReader(ROOT_PATH, dataReader)); + salRegistrations.add(mountInstance.registerOperationalReader(ROOT_PATH, dataReader)); + salRegistrations.add(mountInstance.registerCommitHandler(ROOT_PATH, commitHandler)); + } + + @Override + public void close() { + for (final AutoCloseable reg : Lists.reverse(salRegistrations)) { + closeGracefully(reg); + } + closeGracefully(salProvider); + } + + private void closeGracefully(final AutoCloseable resource) { + if (resource != null) { + try { + resource.close(); + } catch (final Exception e) { + logger.warn("{}: Ignoring exception while closing {}", id, resource, e); + } + } + } +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceSalProvider.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceSalProvider.java new file mode 100644 index 0000000000..01af84c9ac --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceSalProvider.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2014 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.controller.sal.connect.netconf.sal; + +import java.util.Collection; +import java.util.Collections; + +import java.util.concurrent.ExecutorService; +import org.opendaylight.controller.sal.binding.api.BindingAwareBroker; +import org.opendaylight.controller.sal.binding.api.BindingAwareProvider; +import org.opendaylight.controller.sal.binding.api.data.DataProviderService; +import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; +import org.opendaylight.controller.sal.core.api.Broker; +import org.opendaylight.controller.sal.core.api.Provider; +import org.opendaylight.controller.sal.core.api.mount.MountProvisionInstance; +import org.opendaylight.controller.sal.core.api.mount.MountProvisionService; +import org.opendaylight.yangtools.yang.binding.RpcService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; + +final class NetconfDeviceSalProvider implements AutoCloseable, Provider, BindingAwareProvider { + + private static final Logger logger = LoggerFactory.getLogger(NetconfDeviceSalProvider.class); + + private final RemoteDeviceId id; + private final ExecutorService executor; + private volatile MountProvisionInstance mountInstance; + private volatile NetconfDeviceDatastoreAdapter datastoreAdapter; + + public NetconfDeviceSalProvider(final RemoteDeviceId deviceId, final ExecutorService executor) { + this.id = deviceId; + this.executor = executor; + } + + public MountProvisionInstance getMountInstance() { + Preconditions.checkState(mountInstance != null, + "%s: Sal provider was not initialized by sal. Cannot publish notification", id); + return mountInstance; + } + + public NetconfDeviceDatastoreAdapter getDatastoreAdapter() { + Preconditions.checkState(datastoreAdapter != null, + "%s: Sal provider %s was not initialized by sal. Cannot publish notification", id); + return datastoreAdapter; + } + + @Override + public void onSessionInitiated(final Broker.ProviderSession session) { + final MountProvisionService mountService = session.getService(MountProvisionService.class); + if (mountService != null) { + mountInstance = mountService.createOrGetMountPoint(id.getPath()); + } + + logger.debug("{}: (BI)Session with sal established {}", id, session); + } + + @Override + public Collection getProviderFunctionality() { + return Collections.emptySet(); + } + + @Override + public Collection getImplementations() { + return Collections.emptySet(); + } + + @Override + public Collection getFunctionality() { + return Collections.emptySet(); + } + + @Override + public void onSessionInitiated(final BindingAwareBroker.ProviderContext session) { + final DataProviderService dataBroker = session.getSALService(DataProviderService.class); + datastoreAdapter = new NetconfDeviceDatastoreAdapter(id, dataBroker, executor); + + logger.debug("{}: Session with sal established {}", id, session); + } + + @Override + public void onSessionInitialized(final BindingAwareBroker.ConsumerContext session) { + } + + public void close() throws Exception { + mountInstance = null; + datastoreAdapter.close(); + datastoreAdapter = null; + } + +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceTwoPhaseCommitTransaction.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceTwoPhaseCommitTransaction.java new file mode 100644 index 0000000000..41f9fec3c4 --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceTwoPhaseCommitTransaction.java @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2014 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.controller.sal.connect.netconf.sal; + +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_CANDIDATE_QNAME; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_COMMIT_QNAME; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_CONFIG_QNAME; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_ERROR_OPTION_QNAME; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_OPERATION_QNAME; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_RUNNING_QNAME; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_TARGET_QNAME; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.ROLLBACK_ON_ERROR_OPTION; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ExecutionException; + +import org.opendaylight.controller.md.sal.common.api.data.DataCommitHandler.DataCommitTransaction; +import org.opendaylight.controller.md.sal.common.api.data.DataModification; +import org.opendaylight.controller.sal.common.util.RpcErrors; +import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil; +import org.opendaylight.controller.sal.connect.util.FailedRpcResult; +import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; +import org.opendaylight.controller.sal.core.api.RpcImplementation; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.RpcError; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode; +import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +/** + * Remote transaction that delegates data change to remote device using netconf messages. + */ +final class NetconfDeviceTwoPhaseCommitTransaction implements DataCommitTransaction { + + private static final Logger LOG = LoggerFactory.getLogger(NetconfDeviceTwoPhaseCommitTransaction.class); + + private final DataModification modification; + private final RpcImplementation rpc; + private final boolean rollbackSupported; + private final RemoteDeviceId id; + private final CompositeNode targetNode; + + public NetconfDeviceTwoPhaseCommitTransaction(final RemoteDeviceId id, final RpcImplementation rpc, + final DataModification modification, + final boolean candidateSupported, final boolean rollbackOnErrorSupported) { + this.id = id; + this.rpc = Preconditions.checkNotNull(rpc); + this.modification = Preconditions.checkNotNull(modification); + this.targetNode = getTargetNode(candidateSupported); + this.rollbackSupported = rollbackOnErrorSupported; + } + + /** + * Prepare phase, sends 1 or more netconf edit config operations to modify the data + * + * In case of failure or unexpected error response, ExecutionException is thrown + */ + void prepare() throws InterruptedException, ExecutionException { + for (final InstanceIdentifier toRemove : modification.getRemovedConfigurationData()) { + sendDelete(toRemove); + } + for(final Entry toUpdate : modification.getUpdatedConfigurationData().entrySet()) { + sendMerge(toUpdate.getKey(),toUpdate.getValue()); + } + } + + private void sendMerge(final InstanceIdentifier key, final CompositeNode value) throws InterruptedException, ExecutionException { + sendEditRpc(createEditConfigStructure(key, Optional.absent(), Optional.of(value))); + } + + private void sendDelete(final InstanceIdentifier toDelete) throws InterruptedException, ExecutionException { + sendEditRpc(createEditConfigStructure(toDelete, Optional.of("delete"), Optional.absent())); + } + + private void sendEditRpc(final CompositeNode editStructure) throws InterruptedException, ExecutionException { + final ImmutableCompositeNode editConfigRequest = createEditConfigRequest(editStructure); + final RpcResult rpcResult = rpc.invokeRpc(NETCONF_EDIT_CONFIG_QNAME, editConfigRequest).get(); + // TODO 874 add default operation when sending delete + + // Check result + if(rpcResult.isSuccessful() == false) { + throw new ExecutionException( + String.format("%s: Pre-commit rpc failed, request: %s, errors: %s", id, editConfigRequest, rpcResult.getErrors()), null); + } + } + + private ImmutableCompositeNode createEditConfigRequest(final CompositeNode editStructure) { + final CompositeNodeBuilder ret = ImmutableCompositeNode.builder(); + + final Node targetWrapperNode = ImmutableCompositeNode.create(NETCONF_TARGET_QNAME, ImmutableList.>of(targetNode)); + ret.add(targetWrapperNode); + + if(rollbackSupported) { + ret.addLeaf(NETCONF_ERROR_OPTION_QNAME, ROLLBACK_ON_ERROR_OPTION); + } + ret.setQName(NETCONF_EDIT_CONFIG_QNAME); + ret.add(editStructure); + return ret.toInstance(); + } + + private CompositeNode createEditConfigStructure(final InstanceIdentifier dataPath, final Optional operation, + final Optional lastChildOverride) { + Preconditions.checkArgument(dataPath.getPath().isEmpty() == false, "Instance identifier with empty path %s", dataPath); + + List reversedPath = Lists.reverse(dataPath.getPath()); + + // Create deepest edit element with expected edit operation + CompositeNode previous = getDeepestEditElement(reversedPath.get(0), operation, lastChildOverride); + + // Remove already processed deepest child + reversedPath = Lists.newArrayList(reversedPath); + reversedPath.remove(0); + + // Create edit structure in reversed order + for (final PathArgument arg : reversedPath) { + final CompositeNodeBuilder builder = ImmutableCompositeNode.builder(); + builder.setQName(arg.getNodeType()); + + addPredicatesToCompositeNodeBuilder(getPredicates(arg), builder); + + builder.add(previous); + previous = builder.toInstance(); + } + return ImmutableCompositeNode.create(NETCONF_CONFIG_QNAME, ImmutableList.>of(previous)); + } + + private void addPredicatesToCompositeNodeBuilder(final Map predicates, final CompositeNodeBuilder builder) { + for (final Entry entry : predicates.entrySet()) { + builder.addLeaf(entry.getKey(), entry.getValue()); + } + } + + private Map getPredicates(final PathArgument arg) { + Map predicates = Collections.emptyMap(); + if (arg instanceof NodeIdentifierWithPredicates) { + predicates = ((NodeIdentifierWithPredicates) arg).getKeyValues(); + } + return predicates; + } + + private CompositeNode getDeepestEditElement(final PathArgument arg, final Optional operation, final Optional lastChildOverride) { + final CompositeNodeBuilder builder = ImmutableCompositeNode.builder(); + builder.setQName(arg.getNodeType()); + + final Map predicates = getPredicates(arg); + addPredicatesToCompositeNodeBuilder(predicates, builder); + + if (operation.isPresent()) { + builder.setAttribute(NETCONF_OPERATION_QNAME, operation.get()); + } + if (lastChildOverride.isPresent()) { + final List> children = lastChildOverride.get().getValue(); + for(final Node child : children) { + if(!predicates.containsKey(child.getKey())) { + builder.add(child); + } + } + } + + return builder.toInstance(); + } + + /** + * Send commit rpc to finish the transaction + * In case of failure or unexpected error response, ExecutionException is thrown + */ + @Override + public RpcResult finish() { + try { + final RpcResult rpcResult = rpc.invokeRpc(NetconfMessageTransformUtil.NETCONF_COMMIT_QNAME, getCommitRequest()).get(); + return new RpcResultVoidWrapper(rpcResult); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(id + ": Interrupted while waiting for response", e); + } catch (final ExecutionException e) { + LOG.warn("{}: Failed to finish commit operation", id, e); + return new FailedRpcResult<>(RpcErrors.getRpcError(null, null, null, RpcError.ErrorSeverity.ERROR, + id + ": Unexpected operation error during commit operation", RpcError.ErrorType.APPLICATION, e)); + } + } + + private ImmutableCompositeNode getCommitRequest() { + final CompositeNodeBuilder commitInput = ImmutableCompositeNode.builder(); + commitInput.setQName(NETCONF_COMMIT_QNAME); + return commitInput.toInstance(); + } + + @Override + public DataModification getModification() { + return this.modification; + } + + @Override + public RpcResult rollback() throws IllegalStateException { + // TODO BUG-732 implement rollback by sending discard changes + return null; + } + + public CompositeNode getTargetNode(final boolean candidateSupported) { + if(candidateSupported) { + return ImmutableCompositeNode.create(NETCONF_CANDIDATE_QNAME, ImmutableList.>of()); + } else { + return ImmutableCompositeNode.create(NETCONF_RUNNING_QNAME, ImmutableList.>of()); + } + } + + private static final class RpcResultVoidWrapper implements RpcResult { + + private final RpcResult rpcResult; + + public RpcResultVoidWrapper(final RpcResult rpcResult) { + this.rpcResult = rpcResult; + } + + @Override + public boolean isSuccessful() { + return rpcResult.isSuccessful(); + } + + @Override + public Void getResult() { + return null; + } + + @Override + public Collection getErrors() { + return rpcResult.getErrors(); + } + } +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/schema/NetconfDeviceSchemaProviderFactory.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/schema/NetconfDeviceSchemaProviderFactory.java new file mode 100644 index 0000000000..9f844fde3f --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/schema/NetconfDeviceSchemaProviderFactory.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2014 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.controller.sal.connect.netconf.schema; + +import java.io.InputStream; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import javax.annotation.concurrent.ThreadSafe; + +import org.opendaylight.controller.sal.connect.api.SchemaContextProviderFactory; +import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; +import org.opendaylight.controller.sal.core.api.RpcImplementation; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaContextProvider; +import org.opendaylight.yangtools.yang.model.util.repo.SchemaSourceProvider; +import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl; +import org.opendaylight.yangtools.yang.parser.impl.util.YangSourceContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; + +public final class NetconfDeviceSchemaProviderFactory implements SchemaContextProviderFactory { + + private static final Logger logger = LoggerFactory.getLogger(NetconfDeviceSchemaProviderFactory.class); + + private final RemoteDeviceId id; + + public NetconfDeviceSchemaProviderFactory(final RemoteDeviceId id) { + this.id = id; + } + + @Override + public SchemaContextProvider createContextProvider(final Collection capabilities, final SchemaSourceProvider sourceProvider) { + + final YangSourceContext sourceContext = YangSourceContext.createFrom(capabilities, sourceProvider); + + if (sourceContext.getMissingSources().isEmpty() == false) { + logger.warn("{}: Sources for following models are missing {}", id, sourceContext.getMissingSources()); + } + + logger.debug("{}: Trying to create schema context from {}", id, sourceContext.getValidSources()); + final List modelsToParse = YangSourceContext.getValidInputStreams(sourceContext); + + Preconditions.checkState(sourceContext.getValidSources().isEmpty() == false, + "%s: Unable to create schema context, no sources provided by device", id); + try { + final SchemaContext schemaContext = tryToParseContext(modelsToParse); + logger.debug("{}: Schema context successfully created.", id); + return new NetconfSchemaContextProvider(schemaContext); + } catch (final RuntimeException e) { + logger.error("{}: Unable to create schema context, unexpected error", id, e); + throw new IllegalStateException(id + ": Unable to create schema context", e); + } + } + + private static SchemaContext tryToParseContext(final List modelsToParse) { + final YangParserImpl parser = new YangParserImpl(); + final Set models = parser.parseYangModelsFromStreams(modelsToParse); + return parser.resolveSchemaContext(models); + } + + private static final class NetconfSchemaContextProvider implements SchemaContextProvider { + private final SchemaContext schemaContext; + + public NetconfSchemaContextProvider(final SchemaContext schemaContext) { + this.schemaContext = schemaContext; + } + + @Override + public SchemaContext getSchemaContext() { + return schemaContext; + } + } +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/schema/NetconfRemoteSchemaSourceProvider.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/schema/NetconfRemoteSchemaSourceProvider.java new file mode 100644 index 0000000000..b43e03bef7 --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/schema/NetconfRemoteSchemaSourceProvider.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2014 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.controller.sal.connect.netconf.schema; + +import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil; +import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; +import org.opendaylight.controller.sal.core.api.RpcImplementation; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.data.api.SimpleNode; +import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode; +import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder; +import org.opendaylight.yangtools.yang.model.util.repo.SchemaSourceProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; + +public final class NetconfRemoteSchemaSourceProvider implements SchemaSourceProvider { + + public static final QName GET_SCHEMA_QNAME = QName.create(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING, + "get-schema"); + public static final QName GET_DATA_QNAME = QName + .create(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING, "data"); + + private static final Logger logger = LoggerFactory.getLogger(NetconfRemoteSchemaSourceProvider.class); + + private final RpcImplementation rpc; + private final RemoteDeviceId id; + + public NetconfRemoteSchemaSourceProvider(final RemoteDeviceId id, final RpcImplementation rpc) { + this.id = id; + this.rpc = Preconditions.checkNotNull(rpc); + } + + @Override + public Optional getSchemaSource(final String moduleName, final Optional revision) { + final ImmutableCompositeNode getSchemaRequest = createGetSchemaRequest(moduleName, revision); + + logger.trace("{}: Loading YANG schema source for {}:{}", id, moduleName, revision); + try { + final RpcResult schemaReply = rpc.invokeRpc(GET_SCHEMA_QNAME, getSchemaRequest).get(); + if (schemaReply.isSuccessful()) { + final Optional schemaBody = getSchemaFromRpc(id, schemaReply.getResult()); + if (schemaBody.isPresent()) { + logger.debug("{}: YANG Schema successfully retrieved for {}:{}", id, moduleName, revision); + return schemaBody; + } + } else { + logger.warn("{}: YANG schema was not successfully retrieved for {}:{}. Errors: {}", id, moduleName, + revision, schemaReply.getErrors()); + } + return Optional.absent(); + } catch (final InterruptedException e){ + Thread.currentThread().interrupt(); + throw new IllegalStateException(e); + } catch (final Exception e) { + logger.error("{}: YANG schema was not successfully retrieved for {}:{}", id, moduleName, revision, e); + throw new IllegalStateException(e); + } + } + + private ImmutableCompositeNode createGetSchemaRequest(final String moduleName, final Optional revision) { + final CompositeNodeBuilder request = ImmutableCompositeNode.builder(); + request.setQName(GET_SCHEMA_QNAME) + .addLeaf("format", "yang") + .addLeaf("identifier", moduleName); + + if (revision.isPresent()) { + request.addLeaf("version", revision.get()); + } + return request.toInstance(); + } + + private static Optional getSchemaFromRpc(final RemoteDeviceId id, final CompositeNode result) { + if (result == null) { + return Optional.absent(); + } + final SimpleNode simpleNode = result.getFirstSimpleByName(GET_DATA_QNAME.withoutRevision()); + + Preconditions.checkNotNull(simpleNode, + "%s Unexpected response to get-schema, expected response with one child %s, but was %s", + id, GET_DATA_QNAME.withoutRevision(), result); + + final Object potential = simpleNode.getValue(); + return potential instanceof String ? Optional.of((String) potential) : Optional.absent(); + } +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/schema/mapping/NetconfMessageTransformer.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/schema/mapping/NetconfMessageTransformer.java new file mode 100644 index 0000000000..c85a52909d --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/schema/mapping/NetconfMessageTransformer.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2014 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.controller.sal.connect.netconf.schema.mapping; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import javax.activation.UnsupportedDataTypeException; + +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.sal.common.util.Rpcs; +import org.opendaylight.controller.sal.connect.api.MessageTransformer; +import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil; +import org.opendaylight.controller.sal.connect.util.MessageCounter; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.RpcError; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.data.impl.CompositeNodeTOImpl; +import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode; +import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlDocumentUtils; +import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder; +import org.opendaylight.yangtools.yang.model.api.NotificationDefinition; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import com.google.common.base.Optional; + +public class NetconfMessageTransformer implements MessageTransformer { + + public static final String MESSAGE_ID_PREFIX = "m"; + + private Optional schemaContext = Optional.absent(); + private final MessageCounter counter; + + public NetconfMessageTransformer() { + this.counter = new MessageCounter(); + } + + @Override + public synchronized CompositeNode toNotification(final NetconfMessage message) { + if(schemaContext.isPresent()) { + return toNotification(message, schemaContext.get()); + } else { + return XmlDocumentUtils.notificationToDomNodes(message.getDocument(), Optional.>absent()); + } + } + + private static CompositeNode toNotification(final NetconfMessage message, final SchemaContext ctx) { + final Set notifications = ctx.getNotifications(); + final Document document = message.getDocument(); + return XmlDocumentUtils.notificationToDomNodes(document, Optional.fromNullable(notifications)); + } + + @Override + public NetconfMessage toRpcRequest(final QName rpc, final CompositeNode node) { + final CompositeNodeTOImpl rpcPayload = NetconfMessageTransformUtil.wrap( + NetconfMessageTransformUtil.NETCONF_RPC_QNAME, NetconfMessageTransformUtil.flattenInput(node)); + final Document w3cPayload; + try { + w3cPayload = XmlDocumentUtils.toDocument(rpcPayload, XmlDocumentUtils.defaultValueCodecProvider()); + } catch (final UnsupportedDataTypeException e) { + throw new IllegalArgumentException("Unable to create message", e); + } + w3cPayload.getDocumentElement().setAttribute("message-id", counter.getNewMessageId(MESSAGE_ID_PREFIX)); + return new NetconfMessage(w3cPayload); + } + + @Override + public synchronized RpcResult toRpcResult(final NetconfMessage message, final QName rpc) { + if(schemaContext.isPresent()) { + return toRpcResult(message, rpc, schemaContext.get()); + } else { + final CompositeNode node = (CompositeNode) XmlDocumentUtils.toDomNode(message.getDocument()); + return Rpcs.getRpcResult(true, node, Collections.emptySet()); + } + } + + private static RpcResult toRpcResult(final NetconfMessage message, final QName rpc, final SchemaContext context) { + final CompositeNode compositeNode; + + if (NetconfMessageTransformUtil.isDataRetrievalOperation(rpc)) { + + final Element xmlData = NetconfMessageTransformUtil.getDataSubtree(message.getDocument()); + + final List> dataNodes = XmlDocumentUtils.toDomNodes(xmlData, + Optional.of(context.getDataDefinitions()), context); + + final CompositeNodeBuilder it = ImmutableCompositeNode.builder(); + it.setQName(NetconfMessageTransformUtil.NETCONF_RPC_REPLY_QNAME); + it.add(ImmutableCompositeNode.create(NetconfMessageTransformUtil.NETCONF_DATA_QNAME, dataNodes)); + + compositeNode = it.toInstance(); + } else { + // TODO map rpc with schema + compositeNode = (CompositeNode) XmlDocumentUtils.toDomNode(message.getDocument()); + } + + return Rpcs.getRpcResult(true, compositeNode, Collections. emptySet()); + } + + @Override + public synchronized void onGlobalContextUpdated(final SchemaContext schemaContext) { + this.schemaContext = Optional.of(schemaContext); + } +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfMapping.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfMessageTransformUtil.java similarity index 50% rename from opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfMapping.java rename to opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfMessageTransformUtil.java index 2b3a992fc1..0c6ad45fbb 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfMapping.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfMessageTransformUtil.java @@ -5,103 +5,79 @@ * 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.controller.sal.connect.netconf; +package org.opendaylight.controller.sal.connect.netconf.util; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; -import javax.activation.UnsupportedDataTypeException; +import java.util.Map; import javax.annotation.Nullable; import org.opendaylight.controller.netconf.api.NetconfDocumentedException; import org.opendaylight.controller.netconf.api.NetconfMessage; import org.opendaylight.controller.netconf.util.messages.NetconfMessageUtil; import org.opendaylight.controller.netconf.util.xml.XmlUtil; -import org.opendaylight.controller.sal.common.util.Rpcs; import org.opendaylight.yangtools.yang.common.QName; -import org.opendaylight.yangtools.yang.common.RpcError; -import org.opendaylight.yangtools.yang.common.RpcResult; import org.opendaylight.yangtools.yang.data.api.CompositeNode; import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier; -import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates; -import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument; import org.opendaylight.yangtools.yang.data.api.Node; import org.opendaylight.yangtools.yang.data.impl.CompositeNodeTOImpl; import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode; import org.opendaylight.yangtools.yang.data.impl.SimpleNodeTOImpl; import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlDocumentUtils; import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder; -import org.opendaylight.yangtools.yang.model.api.NotificationDefinition; -import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.w3c.dom.Document; import org.w3c.dom.Element; -import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; -public class NetconfMapping { +public class NetconfMessageTransformUtil { - public static URI NETCONF_URI = URI.create("urn:ietf:params:xml:ns:netconf:base:1.0"); - public static String NETCONF_MONITORING_URI = "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring"; - public static URI NETCONF_NOTIFICATION_URI = URI.create("urn:ietf:params:xml:ns:netconf:notification:1.0"); - public static URI NETCONF_ROLLBACK_ON_ERROR_URI = URI.create("urn:ietf:params:netconf:capability:rollback-on-error:1.0"); + private NetconfMessageTransformUtil() { + } + public static final QName IETF_NETCONF_MONITORING = QName.create( + "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring", "2010-10-04", "ietf-netconf-monitoring"); + public static URI NETCONF_URI = URI.create("urn:ietf:params:xml:ns:netconf:base:1.0"); public static QName NETCONF_QNAME = QName.create(NETCONF_URI, null, "netconf"); - public static QName NETCONF_RPC_QNAME = QName.create(NETCONF_QNAME, "rpc"); - public static QName NETCONF_GET_QNAME = QName.create(NETCONF_QNAME, "get"); - public static QName NETCONF_FILTER_QNAME = QName.create(NETCONF_QNAME, "filter"); - public static QName NETCONF_TYPE_QNAME = QName.create(NETCONF_QNAME, "type"); - public static QName NETCONF_GET_CONFIG_QNAME = QName.create(NETCONF_QNAME, "get-config"); - public static QName NETCONF_EDIT_CONFIG_QNAME = QName.create(NETCONF_QNAME, "edit-config"); - public static QName NETCONF_DELETE_CONFIG_QNAME = QName.create(NETCONF_QNAME, "delete-config"); - public static QName NETCONF_OPERATION_QNAME = QName.create(NETCONF_QNAME, "operation"); - public static QName NETCONF_COMMIT_QNAME = QName.create(NETCONF_QNAME, "commit"); - - public static QName NETCONF_CONFIG_QNAME = QName.create(NETCONF_QNAME, "config"); - public static QName NETCONF_SOURCE_QNAME = QName.create(NETCONF_QNAME, "source"); - public static QName NETCONF_TARGET_QNAME = QName.create(NETCONF_QNAME, "target"); - - public static QName NETCONF_CANDIDATE_QNAME = QName.create(NETCONF_QNAME, "candidate"); - public static QName NETCONF_RUNNING_QNAME = QName.create(NETCONF_QNAME, "running"); - - public static QName NETCONF_ERROR_OPTION_QNAME = QName.create(NETCONF_QNAME, "error-option"); - public static String ROLLBACK_ON_ERROR_OPTION = "rollback-on-error"; - - public static QName NETCONF_RPC_REPLY_QNAME = QName.create(NETCONF_QNAME, "rpc-reply"); - public static QName NETCONF_OK_QNAME = QName.create(NETCONF_QNAME, "ok"); public static QName NETCONF_DATA_QNAME = QName.create(NETCONF_QNAME, "data"); - public static QName NETCONF_CREATE_SUBSCRIPTION_QNAME = QName.create(NETCONF_NOTIFICATION_URI, null, - "create-subscription"); - public static QName NETCONF_CANCEL_SUBSCRIPTION_QNAME = QName.create(NETCONF_NOTIFICATION_URI, null, - "cancel-subscription"); - public static QName IETF_NETCONF_MONITORING_MODULE = QName.create(NETCONF_MONITORING_URI, "2010-10-04", - "ietf-netconf-monitoring"); - - static List> RUNNING = Collections.> singletonList(new SimpleNodeTOImpl(NETCONF_RUNNING_QNAME, + public static QName NETCONF_RPC_REPLY_QNAME = QName.create(NETCONF_QNAME, "rpc-reply"); + public static QName NETCONF_ERROR_OPTION_QNAME = QName.create(NETCONF_QNAME, "error-option"); + public static QName NETCONF_RUNNING_QNAME = QName.create(NETCONF_QNAME, "running"); + static List> RUNNING = Collections.> singletonList(new SimpleNodeTOImpl<>(NETCONF_RUNNING_QNAME, null, null)); - + public static QName NETCONF_SOURCE_QNAME = QName.create(NETCONF_QNAME, "source"); public static CompositeNode CONFIG_SOURCE_RUNNING = new CompositeNodeTOImpl(NETCONF_SOURCE_QNAME, null, RUNNING); + public static QName NETCONF_CANDIDATE_QNAME = QName.create(NETCONF_QNAME, "candidate"); + public static QName NETCONF_TARGET_QNAME = QName.create(NETCONF_QNAME, "target"); + public static QName NETCONF_CONFIG_QNAME = QName.create(NETCONF_QNAME, "config"); + public static QName NETCONF_COMMIT_QNAME = QName.create(NETCONF_QNAME, "commit"); + public static QName NETCONF_OPERATION_QNAME = QName.create(NETCONF_QNAME, "operation"); + public static QName NETCONF_EDIT_CONFIG_QNAME = QName.create(NETCONF_QNAME, "edit-config"); + public static QName NETCONF_GET_CONFIG_QNAME = QName.create(NETCONF_QNAME, "get-config"); + public static QName NETCONF_TYPE_QNAME = QName.create(NETCONF_QNAME, "type"); + public static QName NETCONF_FILTER_QNAME = QName.create(NETCONF_QNAME, "filter"); + public static QName NETCONF_GET_QNAME = QName.create(NETCONF_QNAME, "get"); + public static QName NETCONF_RPC_QNAME = QName.create(NETCONF_QNAME, "rpc"); + public static URI NETCONF_ROLLBACK_ON_ERROR_URI = URI + .create("urn:ietf:params:netconf:capability:rollback-on-error:1.0"); + public static String ROLLBACK_ON_ERROR_OPTION = "rollback-on-error"; - static AtomicInteger messageId = new AtomicInteger(0); - - static Node toFilterStructure(final InstanceIdentifier identifier) { + public static Node toFilterStructure(final InstanceIdentifier identifier) { Node previous = null; if (identifier.getPath().isEmpty()) { return null; } - for (org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument component : Lists + for (final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument component : Lists .reverse(identifier.getPath())) { - if (component instanceof NodeIdentifierWithPredicates) { - previous = toNode((NodeIdentifierWithPredicates)component, previous); + if (component instanceof InstanceIdentifier.NodeIdentifierWithPredicates) { + previous = toNode((InstanceIdentifier.NodeIdentifierWithPredicates)component, previous); } else { previous = toNode(component, previous); } @@ -109,9 +85,9 @@ public class NetconfMapping { return filter("subtree", previous); } - static Node toNode(final NodeIdentifierWithPredicates argument, final Node node) { - List> list = new ArrayList<>(); - for (Map.Entry arg : argument.getKeyValues().entrySet()) { + static Node toNode(final InstanceIdentifier.NodeIdentifierWithPredicates argument, final Node node) { + final List> list = new ArrayList<>(); + for (final Map.Entry arg : argument.getKeyValues().entrySet()) { list.add(new SimpleNodeTOImpl(arg.getKey(), null, arg.getValue())); } if (node != null) { @@ -120,51 +96,31 @@ public class NetconfMapping { return new CompositeNodeTOImpl(argument.getNodeType(), null, list); } - static Node toNode(final PathArgument argument, final Node node) { - if (node != null) { - return new CompositeNodeTOImpl(argument.getNodeType(), null, Collections.> singletonList(node)); - } else { - return new SimpleNodeTOImpl(argument.getNodeType(), null, null); - } - } - - static CompositeNode toCompositeNode(final NetconfMessage message, final Optional ctx) { - // TODO: implement general normalization to normalize incoming Netconf - // Message - // for Schema Context counterpart - return null; - } + public static void checkValidReply(final NetconfMessage input, final NetconfMessage output) { + final String inputMsgId = input.getDocument().getDocumentElement().getAttribute("message-id"); + final String outputMsgId = output.getDocument().getDocumentElement().getAttribute("message-id"); - static CompositeNode toNotificationNode(final NetconfMessage message, final Optional ctx) { - if (ctx.isPresent()) { - SchemaContext schemaContext = ctx.get(); - Set notifications = schemaContext.getNotifications(); - Document document = message.getDocument(); - return XmlDocumentUtils.notificationToDomNodes(document, Optional.fromNullable(notifications), ctx.get()); + if(inputMsgId.equals(outputMsgId) == false) { + final String requestXml = XmlUtil.toString(input.getDocument()); + final String responseXml = XmlUtil.toString(output.getDocument()); + throw new IllegalStateException(String.format("Rpc request and reply message IDs must be same. Request: %s, response: %s", requestXml, responseXml)); } - return null; } - static NetconfMessage toRpcMessage(final QName rpc, final CompositeNode node, final Optional ctx) { - CompositeNodeTOImpl rpcPayload = wrap(NETCONF_RPC_QNAME, flattenInput(node)); - Document w3cPayload = null; - try { - w3cPayload = XmlDocumentUtils.toDocument(rpcPayload, XmlDocumentUtils.defaultValueCodecProvider()); - } catch (UnsupportedDataTypeException e) { - throw new IllegalArgumentException("Unable to create message", e); + public static void checkSuccessReply(final NetconfMessage output) throws NetconfDocumentedException { + if(NetconfMessageUtil.isErrorMessage(output)) { + throw new IllegalStateException(String.format("Response contains error: %s", XmlUtil.toString(output.getDocument()))); } - w3cPayload.getDocumentElement().setAttribute("message-id", "m-" + messageId.getAndIncrement()); - return new NetconfMessage(w3cPayload); } - static CompositeNode flattenInput(final CompositeNode node) { + public static CompositeNode flattenInput(final CompositeNode node) { final QName inputQName = QName.create(node.getNodeType(), "input"); - CompositeNode input = node.getFirstCompositeByName(inputQName); + final CompositeNode input = node.getFirstCompositeByName(inputQName); if (input == null) return node; if (input instanceof CompositeNode) { - List> nodes = ImmutableList.> builder() // + final List> nodes = ImmutableList.> builder() // .addAll(input.getValue()) // .addAll(Collections2.filter(node.getValue(), new Predicate>() { @Override @@ -180,43 +136,25 @@ public class NetconfMapping { return input; } - static RpcResult toRpcResult(final NetconfMessage message, final QName rpc, final Optional context) { - CompositeNode rawRpc; - if (context.isPresent()) - if (isDataRetrieQNameReply(rpc)) { - - Element xmlData = getDataSubtree(message.getDocument()); - - List> dataNodes = XmlDocumentUtils.toDomNodes(xmlData, - Optional.of(context.get().getDataDefinitions())); - - CompositeNodeBuilder it = ImmutableCompositeNode.builder(); - it.setQName(NETCONF_RPC_REPLY_QNAME); - it.add(ImmutableCompositeNode.create(NETCONF_DATA_QNAME, dataNodes)); - - rawRpc = it.toInstance(); - // sys(xmlData) - } else { - rawRpc = toCompositeNode(message, context); - } - else { - rawRpc = (CompositeNode) toCompositeNode(message.getDocument()); + static Node toNode(final InstanceIdentifier.PathArgument argument, final Node node) { + if (node != null) { + return new CompositeNodeTOImpl(argument.getNodeType(), null, Collections.> singletonList(node)); + } else { + return new SimpleNodeTOImpl(argument.getNodeType(), null, null); } - // rawRpc. - return Rpcs.getRpcResult(true, rawRpc, Collections. emptySet()); } - static Element getDataSubtree(final Document doc) { + public static Element getDataSubtree(final Document doc) { return (Element) doc.getElementsByTagNameNS(NETCONF_URI.toString(), "data").item(0); } - static boolean isDataRetrieQNameReply(final QName it) { - return NETCONF_URI == it.getNamespace() - && (it.getLocalName() == NETCONF_GET_CONFIG_QNAME.getLocalName() || it.getLocalName() == NETCONF_GET_QNAME - .getLocalName()); + public static boolean isDataRetrievalOperation(final QName rpc) { + return NETCONF_URI == rpc.getNamespace() + && (rpc.getLocalName().equals(NETCONF_GET_CONFIG_QNAME.getLocalName()) || rpc.getLocalName().equals( + NETCONF_GET_QNAME.getLocalName())); } - static CompositeNodeTOImpl wrap(final QName name, final Node node) { + public static CompositeNodeTOImpl wrap(final QName name, final Node node) { if (node != null) { return new CompositeNodeTOImpl(name, null, Collections.> singletonList(node)); } else { @@ -224,7 +162,7 @@ public class NetconfMapping { } } - static CompositeNodeTOImpl wrap(final QName name, final Node additional, final Node node) { + public static CompositeNodeTOImpl wrap(final QName name, final Node additional, final Node node) { if (node != null) { return new CompositeNodeTOImpl(name, null, ImmutableList.of(additional, node)); } else { @@ -233,7 +171,7 @@ public class NetconfMapping { } static ImmutableCompositeNode filter(final String type, final Node node) { - CompositeNodeBuilder it = ImmutableCompositeNode.builder(); // + final CompositeNodeBuilder it = ImmutableCompositeNode.builder(); // it.setQName(NETCONF_FILTER_QNAME); it.setAttribute(NETCONF_TYPE_QNAME, type); if (node != null) { @@ -243,24 +181,4 @@ public class NetconfMapping { } } - public static Node toCompositeNode(final Document document) { - return XmlDocumentUtils.toDomNode(document); - } - - public static void checkValidReply(final NetconfMessage input, final NetconfMessage output) { - String inputMsgId = input.getDocument().getDocumentElement().getAttribute("message-id"); - String outputMsgId = output.getDocument().getDocumentElement().getAttribute("message-id"); - - if(inputMsgId.equals(outputMsgId) == false) { - String requestXml = XmlUtil.toString(input.getDocument()); - String responseXml = XmlUtil.toString(output.getDocument()); - throw new IllegalStateException(String.format("Rpc request and reply message IDs must be same. Request: %s, response: %s", requestXml, responseXml)); - } - } - - public static void checkSuccessReply(final NetconfMessage output) throws NetconfDocumentedException { - if(NetconfMessageUtil.isErrorMessage(output)) { - throw new IllegalStateException(String.format("Response contains error: %s", XmlUtil.toString(output.getDocument()))); - } - } } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/util/FailedRpcResult.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/util/FailedRpcResult.java new file mode 100644 index 0000000000..49b16d4cfb --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/util/FailedRpcResult.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2014 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.controller.sal.connect.util; + +import java.util.Collection; +import java.util.Collections; + +import org.opendaylight.yangtools.yang.common.RpcError; +import org.opendaylight.yangtools.yang.common.RpcResult; + +public final class FailedRpcResult implements RpcResult { + + private final RpcError rpcError; + + public FailedRpcResult(final RpcError rpcError) { + this.rpcError = rpcError; + } + + @Override + public boolean isSuccessful() { + return false; + } + + @Override + public T getResult() { + return null; + } + + @Override + public Collection getErrors() { + return Collections.singletonList(rpcError); + } +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/util/MessageCounter.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/util/MessageCounter.java new file mode 100644 index 0000000000..2b2f6a9253 --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/util/MessageCounter.java @@ -0,0 +1,21 @@ +package org.opendaylight.controller.sal.connect.util; + +import java.util.concurrent.atomic.AtomicInteger; + +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; + +public class MessageCounter { + final AtomicInteger messageId = new AtomicInteger(0); + + private static final String messageIdBlueprint = "%s-%s"; + + public String getNewMessageId(final String prefix) { + Preconditions.checkArgument(Strings.isNullOrEmpty(prefix) == false, "Null or empty prefix"); + return String.format(messageIdBlueprint, prefix, getNewMessageId()); + } + + public String getNewMessageId() { + return Integer.toString(messageId.getAndIncrement()); + } +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/util/RemoteDeviceId.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/util/RemoteDeviceId.java new file mode 100644 index 0000000000..4670846c7c --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/util/RemoteDeviceId.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2014 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.controller.sal.connect.util; + +import org.opendaylight.controller.config.api.ModuleIdentifier; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +import com.google.common.base.Preconditions; +import org.opendaylight.yangtools.yang.common.QName; + +public class RemoteDeviceId { + + private final String name; + private final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier path; + private final InstanceIdentifier bindingPath; + private final NodeKey key; + + public RemoteDeviceId(final ModuleIdentifier identifier) { + this(Preconditions.checkNotNull(identifier).getInstanceName()); + } + + public RemoteDeviceId(final String name) { + Preconditions.checkNotNull(name); + this.name = name; + this.key = new NodeKey(new NodeId(name)); + this.path = createBIPath(name); + this.bindingPath = createBindingPath(key); + } + + private static InstanceIdentifier createBindingPath(final NodeKey key) { + return InstanceIdentifier.builder(Nodes.class).child(Node.class, key).build(); + } + + private static org.opendaylight.yangtools.yang.data.api.InstanceIdentifier createBIPath(final String name) { + final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.InstanceIdentifierBuilder builder = + org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.builder(); + builder.node(Nodes.QNAME).nodeWithKey(Node.QNAME, QName.create(Node.QNAME.getNamespace(), Node.QNAME.getRevision(), "id"), name); + + return builder.build(); + } + + public String getName() { + return name; + } + + public InstanceIdentifier getBindingPath() { + return bindingPath; + } + + public org.opendaylight.yangtools.yang.data.api.InstanceIdentifier getPath() { + return path; + } + + public NodeKey getBindingKey() { + return key; + } + + @Override + public String toString() { + return "RemoteDevice{" + name +'}'; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (!(o instanceof RemoteDeviceId)) return false; + + final RemoteDeviceId that = (RemoteDeviceId) o; + + if (!name.equals(that.name)) return false; + if (!bindingPath.equals(that.bindingPath)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = name.hashCode(); + result = 31 * result + bindingPath.hashCode(); + return result; + } +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/util/package-info.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/util/package-info.java new file mode 100644 index 0000000000..21b9d3a436 --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/util/package-info.java @@ -0,0 +1,6 @@ +/** + * Utility classes for remote connectors e.g. netconf connector + * + * TODO extract into separate bundle when another connector is implemented e.g. restconf connector + */ +package org.opendaylight.controller.sal.connect.util; diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang b/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang index d4dad11ec3..c59c41c437 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang @@ -3,10 +3,11 @@ module odl-sal-netconf-connector-cfg { namespace "urn:opendaylight:params:xml:ns:yang:controller:md:sal:connector:netconf"; prefix "sal-netconf"; - import config { prefix config; revision-date 2013-04-05; } - import threadpool {prefix th;} - import netty {prefix netty;} - import opendaylight-md-sal-dom {prefix dom;} + import config { prefix config; revision-date 2013-04-05; } + import threadpool {prefix th;} + import netty {prefix netty;} + import opendaylight-md-sal-dom {prefix dom;} + import opendaylight-md-sal-binding {prefix md-sal-binding; revision-date 2013-10-28;} import odl-netconf-cfg { prefix cfg-net; revision-date 2014-04-08; } description @@ -22,7 +23,6 @@ module odl-sal-netconf-connector-cfg { config:java-name-prefix NetconfConnector; } - grouping server { leaf address { type string; @@ -33,7 +33,6 @@ module odl-sal-netconf-connector-cfg { } } - augment "/config:modules/config:module/config:configuration" { case sal-netconf-connector { when "/config:modules/config:module/config:type = 'sal-netconf-connector'"; @@ -67,6 +66,16 @@ module odl-sal-netconf-connector-cfg { } } + container binding-registry { + uses config:service-ref { + refine type { + // FIXME BUG-944 make mandatory (remove backwards compatibility) + mandatory false; + config:required-identity md-sal-binding:binding-broker-osgi-registry; + } + } + } + // FIXME BUG-944 remove backwards compatibility // Deprecated, replaced by client dispatcher. // This dependency will be removed in near future and all configurations of netconf-connector need to be changed to use dispatcher dependency. @@ -99,6 +108,18 @@ module odl-sal-netconf-connector-cfg { } } + container processing-executor { + uses config:service-ref { + refine type { + // FIXME BUG-944 make mandatory (remove backwards compatibility) + mandatory false; + config:required-identity th:threadpool; + } + } + + description "Makes up for flaws in netty threading design"; + } + // Replaces thread group dependencies container client-dispatcher { uses config:service-ref { @@ -122,9 +143,16 @@ module odl-sal-netconf-connector-cfg { } leaf between-attempts-timeout-millis { - description "Timeout in milliseconds to wait between connection attempts."; + description "Initial timeout in milliseconds to wait between connection attempts. Will be multiplied by sleep-factor with every additional attempt"; type uint16; - default 10000; + default 2000; + } + + leaf sleep-factor { + type decimal64 { + fraction-digits 1; + } + default 1.5; } } } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/NetconfDeviceTest.java b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/NetconfDeviceTest.java new file mode 100644 index 0000000000..5ac32b5b3c --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/NetconfDeviceTest.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2014 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.controller.sal.connect.netconf; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.junit.Test; +import org.mockito.Mockito; +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; +import org.opendaylight.controller.sal.common.util.Rpcs; +import org.opendaylight.controller.sal.connect.api.MessageTransformer; +import org.opendaylight.controller.sal.connect.api.RemoteDeviceCommunicator; +import org.opendaylight.controller.sal.connect.api.RemoteDeviceHandler; +import org.opendaylight.controller.sal.connect.api.SchemaContextProviderFactory; +import org.opendaylight.controller.sal.connect.api.SchemaSourceProviderFactory; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities; +import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil; +import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; +import org.opendaylight.controller.sal.core.api.RpcImplementation; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.RpcError; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaContextProvider; +import org.opendaylight.yangtools.yang.model.util.repo.SchemaSourceProvider; +import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl; + +import com.google.common.base.Optional; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.Futures; + +public class NetconfDeviceTest { + + private static final NetconfMessage netconfMessage; + private static final CompositeNode compositeNode; + + static { + try { + netconfMessage = mockClass(NetconfMessage.class); + compositeNode = mockClass(CompositeNode.class); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + private static final RpcResult rpcResult = Rpcs.getRpcResult(true, netconfMessage, Collections.emptySet()); + private static final RpcResult rpcResultC = Rpcs.getRpcResult(true, compositeNode, Collections.emptySet()); + + public static final String TEST_NAMESPACE = "test:namespace"; + public static final String TEST_MODULE = "test-module"; + public static final String TEST_REVISION = "2013-07-22"; + + @Test + public void testNetconfDeviceWithoutMonitoring() throws Exception { + final RemoteDeviceHandler facade = getFacade(); + final RemoteDeviceCommunicator listener = getListener(); + + final NetconfDevice device = new NetconfDevice(getId(), facade, getExecutor(), getMessageTransformer(), getSchemaContextProviderFactory(), getSourceProviderFactory()); + device.onRemoteSessionUp(getSessionCaps(false, Collections.emptyList()), listener); + + Mockito.verify(facade, Mockito.timeout(5000)).onDeviceDisconnected(); + } + + @Test + public void testNetconfDeviceReconnect() throws Exception { + final RemoteDeviceHandler facade = getFacade(); + final RemoteDeviceCommunicator listener = getListener(); + + final SchemaContextProviderFactory schemaContextProviderFactory = getSchemaContextProviderFactory(); + final SchemaSourceProviderFactory sourceProviderFactory = getSourceProviderFactory(); + final MessageTransformer messageTransformer = getMessageTransformer(); + + final NetconfDevice device = new NetconfDevice(getId(), facade, getExecutor(), messageTransformer, schemaContextProviderFactory, sourceProviderFactory); + final NetconfSessionCapabilities sessionCaps = getSessionCaps(true, + Lists.newArrayList(TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION)); + device.onRemoteSessionUp(sessionCaps, listener); + + verify(sourceProviderFactory, timeout(5000)).createSourceProvider(any(RpcImplementation.class)); + verify(schemaContextProviderFactory, timeout(5000)).createContextProvider(any(Collection.class), any(SchemaSourceProvider.class)); + verify(messageTransformer, timeout(5000)).onGlobalContextUpdated(any(SchemaContext.class)); + verify(facade, timeout(5000)).onDeviceConnected(any(SchemaContextProvider.class), any(NetconfSessionCapabilities.class), any(RpcImplementation.class)); + + device.onRemoteSessionDown(); + verify(facade, timeout(5000)).onDeviceDisconnected(); + + device.onRemoteSessionUp(sessionCaps, listener); + + verify(sourceProviderFactory, timeout(5000).times(2)).createSourceProvider(any(RpcImplementation.class)); + verify(schemaContextProviderFactory, timeout(5000).times(2)).createContextProvider(any(Collection.class), any(SchemaSourceProvider.class)); + verify(messageTransformer, timeout(5000).times(2)).onGlobalContextUpdated(any(SchemaContext.class)); + verify(facade, timeout(5000).times(2)).onDeviceConnected(any(SchemaContextProvider.class), any(NetconfSessionCapabilities.class), any(RpcImplementation.class)); + } + + private SchemaContextProviderFactory getSchemaContextProviderFactory() { + final SchemaContextProviderFactory schemaContextProviderFactory = mockClass(SchemaContextProviderFactory.class); + doReturn(new SchemaContextProvider() { + @Override + public SchemaContext getSchemaContext() { + return getSchema(); + } + }).when(schemaContextProviderFactory).createContextProvider(any(Collection.class), any(SchemaSourceProvider.class)); + return schemaContextProviderFactory; + } + + public static SchemaContext getSchema() { + final YangParserImpl parser = new YangParserImpl(); + final List modelsToParse = Lists.newArrayList( + NetconfDeviceTest.class.getResourceAsStream("/schemas/test-module.yang") + ); + final Set models = parser.parseYangModelsFromStreams(modelsToParse); + return parser.resolveSchemaContext(models); + } + + private RemoteDeviceHandler getFacade() throws Exception { + final RemoteDeviceHandler remoteDeviceHandler = mockCloseableClass(RemoteDeviceHandler.class); + doNothing().when(remoteDeviceHandler).onDeviceConnected(any(SchemaContextProvider.class), any(NetconfSessionCapabilities.class), any(RpcImplementation.class)); + doNothing().when(remoteDeviceHandler).onDeviceDisconnected(); + return remoteDeviceHandler; + } + + private T mockCloseableClass(final Class remoteDeviceHandlerClass) throws Exception { + final T mock = mockClass(remoteDeviceHandlerClass); + doNothing().when(mock).close(); + return mock; + } + + public SchemaSourceProviderFactory getSourceProviderFactory() { + final SchemaSourceProviderFactory mock = mockClass(SchemaSourceProviderFactory.class); + + final SchemaSourceProvider schemaSourceProvider = mockClass(SchemaSourceProvider.class); + doReturn(Optional.absent()).when(schemaSourceProvider).getSchemaSource(anyString(), any(Optional.class)); + + doReturn(schemaSourceProvider).when(mock).createSourceProvider(any(RpcImplementation.class)); + return mock; + } + + private static T mockClass(final Class remoteDeviceHandlerClass) { + final T mock = Mockito.mock(remoteDeviceHandlerClass); + Mockito.doReturn(remoteDeviceHandlerClass.getSimpleName()).when(mock).toString(); + return mock; + } + + public RemoteDeviceId getId() { + return new RemoteDeviceId("test-D"); + } + + public ExecutorService getExecutor() { + return Executors.newSingleThreadExecutor(); + } + + public MessageTransformer getMessageTransformer() throws Exception { + final MessageTransformer messageTransformer = mockClass(MessageTransformer.class); + doReturn(netconfMessage).when(messageTransformer).toRpcRequest(any(QName.class), any(CompositeNode.class)); + doReturn(rpcResultC).when(messageTransformer).toRpcResult(any(NetconfMessage.class), any(QName.class)); + doNothing().when(messageTransformer).onGlobalContextUpdated(any(SchemaContext.class)); + return messageTransformer; + } + + public NetconfSessionCapabilities getSessionCaps(final boolean addMonitor, final Collection additionalCapabilities) { + final ArrayList capabilities = Lists.newArrayList( + XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_0, + XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_1); + + if(addMonitor) { + capabilities.add(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING.getNamespace().toString()); + } + + capabilities.addAll(additionalCapabilities); + + return NetconfSessionCapabilities.fromStrings( + capabilities); + } + + public RemoteDeviceCommunicator getListener() throws Exception { + final RemoteDeviceCommunicator remoteDeviceCommunicator = mockCloseableClass(RemoteDeviceCommunicator.class); + doReturn(Futures.immediateFuture(rpcResult)).when(remoteDeviceCommunicator).sendRequest(any(NetconfMessage.class), any(QName.class)); + return remoteDeviceCommunicator; + } +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-netconf-connector/src/test/resources/schemas/test-module.yang b/opendaylight/md-sal/sal-netconf-connector/src/test/resources/schemas/test-module.yang new file mode 100644 index 0000000000..cd732fca70 --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/test/resources/schemas/test-module.yang @@ -0,0 +1,18 @@ +module test-module { + yang-version 1; + namespace "test:namespace"; + prefix "tt"; + + description + "Types for testing"; + + revision "2013-07-22"; + + + container c { + leaf a { + type string; + } + } + +} diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonReader.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonReader.java index 593c104dfd..1f7b061e92 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonReader.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonReader.java @@ -31,6 +31,12 @@ class JsonReader { JsonParser parser = new JsonParser(); JsonElement rootElement = parser.parse(new InputStreamReader(entityStream)); + if( rootElement.isJsonNull() ) + { + //no content, so return null to indicate no input + return null; + } + if (!rootElement.isJsonObject()) { throw new UnsupportedFormatException("Root element of Json has to be Object"); } diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/XmlReader.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/XmlReader.java index 5b95f0de1a..171805a179 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/XmlReader.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/XmlReader.java @@ -9,6 +9,8 @@ package org.opendaylight.controller.sal.rest.impl; import static com.google.common.base.Preconditions.checkArgument; +import java.io.BufferedInputStream; +import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.util.Stack; @@ -32,7 +34,17 @@ public class XmlReader { private final static XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); private XMLEventReader eventReader; - public CompositeNodeWrapper read(InputStream entityStream) throws XMLStreamException, UnsupportedFormatException { + public CompositeNodeWrapper read(InputStream entityStream) throws XMLStreamException, + UnsupportedFormatException, + IOException { + //Get an XML stream which can be marked, and reset, so we can check and see if there is + //any content being provided. + entityStream = getMarkableStream(entityStream); + + if( isInputStreamEmpty( entityStream ) ) { + return null; + } + eventReader = xmlInputFactory.createXMLEventReader(entityStream); if (eventReader.hasNext()) { @@ -91,6 +103,31 @@ public class XmlReader { return root; } + /** + * If the input stream is not markable, then it wraps the input stream with a buffered stream, + * which is mark able. That way we can check if the stream is empty safely. + * @param entityStream + * @return + */ + private InputStream getMarkableStream(InputStream entityStream) { + if( !entityStream.markSupported() ) + { + entityStream = new BufferedInputStream( entityStream ); + } + return entityStream; + } + + private boolean isInputStreamEmpty(InputStream entityStream) + throws IOException { + boolean isEmpty = false; + entityStream.mark( 1 ); + if( entityStream.read() == -1 ){ + isEmpty = true; + } + entityStream.reset(); + return isEmpty; + } + private boolean isSimpleNodeEvent(final XMLEvent event) throws XMLStreamException { checkArgument(event != null, "XML Event cannot be NULL!"); if (event.isStartElement()) { diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.java index ad682bc829..c0ce90e15d 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.java @@ -8,13 +8,6 @@ */ package org.opendaylight.controller.sal.restconf.impl; -import com.google.common.base.Objects; -import com.google.common.base.Preconditions; -import com.google.common.base.Splitter; -import com.google.common.base.Strings; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; - import java.net.URI; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -38,21 +31,11 @@ import org.opendaylight.controller.md.sal.common.api.TransactionStatus; import org.opendaylight.controller.sal.core.api.mount.MountInstance; import org.opendaylight.controller.sal.rest.api.Draft02; import org.opendaylight.controller.sal.rest.api.RestconfService; +import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag; +import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType; import org.opendaylight.controller.sal.restconf.rpc.impl.BrokerRpcExecutor; import org.opendaylight.controller.sal.restconf.rpc.impl.MountPointRpcExecutor; import org.opendaylight.controller.sal.restconf.rpc.impl.RpcExecutor; -import org.opendaylight.controller.sal.restconf.impl.BrokerFacade; -import org.opendaylight.controller.sal.restconf.impl.CompositeNodeWrapper; -import org.opendaylight.controller.sal.restconf.impl.ControllerContext; -import org.opendaylight.controller.sal.restconf.impl.EmptyNodeWrapper; -import org.opendaylight.controller.sal.restconf.impl.IdentityValuesDTO; -import org.opendaylight.controller.sal.restconf.impl.InstanceIdWithSchemaNode; -import org.opendaylight.controller.sal.restconf.impl.NodeWrapper; -import org.opendaylight.controller.sal.restconf.impl.RestCodec; -import org.opendaylight.controller.sal.restconf.impl.SimpleNodeWrapper; -import org.opendaylight.controller.sal.restconf.impl.StructuredData; -import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag; -import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType; import org.opendaylight.controller.sal.streams.listeners.ListenerAdapter; import org.opendaylight.controller.sal.streams.listeners.Notificator; import org.opendaylight.controller.sal.streams.websockets.WebSocketServer; @@ -84,6 +67,13 @@ import org.opendaylight.yangtools.yang.model.util.EmptyType; import org.opendaylight.yangtools.yang.parser.builder.impl.ContainerSchemaNodeBuilder; import org.opendaylight.yangtools.yang.parser.builder.impl.LeafSchemaNodeBuilder; +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import com.google.common.base.Splitter; +import com.google.common.base.Strings; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + public class RestconfImpl implements RestconfService { private final static RestconfImpl INSTANCE = new RestconfImpl(); @@ -401,13 +391,36 @@ public class RestconfImpl implements RestconfService { URI rpcNamespace = rpcName.getNamespace(); if (Objects.equal(rpcNamespace.toString(), SAL_REMOTE_NAMESPACE) && Objects.equal(rpcName.getLocalName(), SAL_REMOTE_RPC_SUBSRCIBE)) { - return invokeSalRemoteRpcSubscribeRPC(payload, rpc.getRpcDefinition()); } + validateInput( rpc.getRpcDefinition().getInput(), payload ); + return callRpc(rpc, payload); } + private void validateInput(DataSchemaNode inputSchema, CompositeNode payload) { + if( inputSchema != null && payload == null ) + { + //expected a non null payload + throw new RestconfDocumentedException( "Input is required.", + ErrorType.PROTOCOL, + ErrorTag.MALFORMED_MESSAGE ); + } + else if( inputSchema == null && payload != null ) + { + //did not expect any input + throw new RestconfDocumentedException( "No input expected.", + ErrorType.PROTOCOL, + ErrorTag.MALFORMED_MESSAGE ); + } + //else + //{ + //TODO: Validate "mandatory" and "config" values here??? Or should those be + // validate in a more central location inside MD-SAL core. + //} + } + private StructuredData invokeSalRemoteRpcSubscribeRPC(final CompositeNode payload, final RpcDefinition rpc) { final CompositeNode value = this.normalizeNode(payload, rpc.getInput(), null); @@ -455,8 +468,7 @@ public class RestconfImpl implements RestconfService { throw new RestconfDocumentedException( "Content must be empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE ); } - final RpcExecutor rpc = resolveIdentifierInInvokeRpc(identifier); - return callRpc(rpc, null); + return invokeRpc( identifier, (CompositeNode)null ); } private RpcExecutor resolveIdentifierInInvokeRpc(final String identifier) { @@ -586,6 +598,9 @@ public class RestconfImpl implements RestconfService { @Override public Response updateConfigurationData(final String identifier, final CompositeNode payload) { final InstanceIdWithSchemaNode iiWithData = this.controllerContext.toInstanceIdentifier(identifier); + + validateInput(iiWithData.getSchemaNode(), payload); + MountInstance mountPoint = iiWithData.getMountPoint(); final CompositeNode value = this.normalizeNode(payload, iiWithData.getSchemaNode(), mountPoint); RpcResult status = null; @@ -610,6 +625,12 @@ public class RestconfImpl implements RestconfService { @Override public Response createConfigurationData(final String identifier, final CompositeNode payload) { + if( payload == null ) { + throw new RestconfDocumentedException( "Input is required.", + ErrorType.PROTOCOL, + ErrorTag.MALFORMED_MESSAGE ); + } + URI payloadNS = this.namespace(payload); if (payloadNS == null) { throw new RestconfDocumentedException( @@ -685,6 +706,12 @@ public class RestconfImpl implements RestconfService { @Override public Response createConfigurationData(final CompositeNode payload) { + if( payload == null ) { + throw new RestconfDocumentedException( "Input is required.", + ErrorType.PROTOCOL, + ErrorTag.MALFORMED_MESSAGE ); + } + URI payloadNS = this.namespace(payload); if (payloadNS == null) { throw new RestconfDocumentedException( diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/json/to/cnsn/test/JsonToCnSnTest.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/json/to/cnsn/test/JsonToCnSnTest.java index 3c70cca0f8..47e329cc3e 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/json/to/cnsn/test/JsonToCnSnTest.java +++ b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/json/to/cnsn/test/JsonToCnSnTest.java @@ -9,8 +9,11 @@ package org.opendaylight.controller.sal.restconf.impl.json.to.cnsn.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -176,6 +179,14 @@ public class JsonToCnSnTest { } + @Test + public void testJsonBlankInput() throws Exception{ + InputStream inputStream = new ByteArrayInputStream( "".getBytes() ); + CompositeNode compositeNode = + JsonToCompositeNodeProvider.INSTANCE.readFrom(null, null, null, null, null, inputStream); + assertNull( compositeNode ); + } + /** * Tests whether namespace stay unchanged if concrete values are * present in composite or simple node and if the method for update is diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/InvokeRpcMethodTest.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/InvokeRpcMethodTest.java index c0c86c3f25..910ca8e20a 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/InvokeRpcMethodTest.java +++ b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/InvokeRpcMethodTest.java @@ -308,7 +308,7 @@ public class InvokeRpcMethodTest { ListenableFuture> mockListener = mock( ListenableFuture.class ); when( mockListener.get() ).thenReturn( rpcResult ); - QName cancelToastQName = QName.create( "cancelToast" ); + QName cancelToastQName = QName.create( "namespace", "2014-05-28", "cancelToast" ); RpcDefinition mockRpc = mock( RpcDefinition.class ); when( mockRpc.getQName() ).thenReturn( cancelToastQName ); diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestPostOperationTest.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestPostOperationTest.java index ce460fe474..cfbc9fdb76 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestPostOperationTest.java +++ b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestPostOperationTest.java @@ -149,6 +149,8 @@ public class RestPostOperationTest extends JerseyTest { mockCommitConfigurationDataPostMethod(TransactionStatus.FAILED); assertEquals(500, post(uri, MediaType.APPLICATION_XML, xmlDataInterfaceAbsolutePath)); + + assertEquals( 400, post(uri, MediaType.APPLICATION_JSON, "" )); } @Test @@ -172,6 +174,8 @@ public class RestPostOperationTest extends JerseyTest { assertEquals(204, post(uri, Draft02.MediaTypes.DATA + XML, xmlData4)); uri = "/config/ietf-interfaces:interfaces/interface/0/yang-ext:mount/test-module:cont"; assertEquals(204, post(uri, Draft02.MediaTypes.DATA + XML, xmlData3)); + + assertEquals( 400, post(uri, MediaType.APPLICATION_JSON, "" )); } private void mockInvokeRpc(CompositeNode result, boolean sucessful) { diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestPutOperationTest.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestPutOperationTest.java index 3af2945526..77b39b7352 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestPutOperationTest.java +++ b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestPutOperationTest.java @@ -22,6 +22,7 @@ import java.util.concurrent.Future; import javax.ws.rs.client.Entity; import javax.ws.rs.core.Application; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; @@ -31,6 +32,7 @@ import org.opendaylight.controller.md.sal.common.api.TransactionStatus; import org.opendaylight.controller.sal.core.api.mount.MountInstance; import org.opendaylight.controller.sal.core.api.mount.MountService; import org.opendaylight.controller.sal.rest.impl.JsonToCompositeNodeProvider; +import org.opendaylight.controller.sal.rest.impl.RestconfDocumentedExceptionMapper; import org.opendaylight.controller.sal.rest.impl.StructuredDataToJsonProvider; import org.opendaylight.controller.sal.rest.impl.StructuredDataToXmlProvider; import org.opendaylight.controller.sal.rest.impl.XmlToCompositeNodeProvider; @@ -86,6 +88,7 @@ public class RestPutOperationTest extends JerseyTest { resourceConfig = resourceConfig.registerInstances(restconfImpl, StructuredDataToXmlProvider.INSTANCE, StructuredDataToJsonProvider.INSTANCE, XmlToCompositeNodeProvider.INSTANCE, JsonToCompositeNodeProvider.INSTANCE); + resourceConfig.registerClasses( RestconfDocumentedExceptionMapper.class ); return resourceConfig; } @@ -100,6 +103,15 @@ public class RestPutOperationTest extends JerseyTest { mockCommitConfigurationDataPutMethod(TransactionStatus.FAILED); assertEquals(500, put(uri, MediaType.APPLICATION_XML, xmlData)); + + assertEquals( 400, put(uri, MediaType.APPLICATION_JSON, "" )); + } + + @Test + public void putConfigStatusCodesEmptyBody() throws UnsupportedEncodingException { + String uri = "/config/ietf-interfaces:interfaces/interface/eth0"; + Response resp = target(uri).request( MediaType.APPLICATION_JSON).put(Entity.entity( "", MediaType.APPLICATION_JSON)); + assertEquals( 400, put(uri, MediaType.APPLICATION_JSON, "" )); } @Test diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/xml/to/cnsn/test/XmlToCnSnTest.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/xml/to/cnsn/test/XmlToCnSnTest.java index 5008d28bbf..5cda4a7f52 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/xml/to/cnsn/test/XmlToCnSnTest.java +++ b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/xml/to/cnsn/test/XmlToCnSnTest.java @@ -9,8 +9,12 @@ package org.opendaylight.controller.sal.restconf.impl.xml.to.cnsn.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import java.io.ByteArrayInputStream; +import java.io.InputStream; + import org.junit.BeforeClass; import org.junit.Test; import org.opendaylight.controller.sal.rest.impl.XmlToCompositeNodeProvider; @@ -52,4 +56,27 @@ public class XmlToCnSnTest extends YangAndXmlAndDataSchemaLoader { assertEquals("121", lf2.getValue()); } + @Test + public void testXmlBlankInput() throws Exception{ + InputStream inputStream = new ByteArrayInputStream( "".getBytes() ); + CompositeNode compositeNode = + XmlToCompositeNodeProvider.INSTANCE.readFrom(null, null, null, null, null, inputStream); + + assertNull( compositeNode ); + } + + @Test + public void testXmlBlankInputUnmarkableStream() throws Exception{ + InputStream inputStream = new ByteArrayInputStream( "".getBytes() ){ + @Override + public boolean markSupported() { + return false; + } + }; + CompositeNode compositeNode = + XmlToCompositeNodeProvider.INSTANCE.readFrom(null, null, null, null, null, inputStream); + + assertNull( compositeNode ); + } + } diff --git a/opendaylight/md-sal/statistics-manager/src/main/java/org/opendaylight/controller/md/statistics/manager/NodeStatisticsHandler.java b/opendaylight/md-sal/statistics-manager/src/main/java/org/opendaylight/controller/md/statistics/manager/NodeStatisticsHandler.java index dbcbab982a..7d9cc7ecbd 100644 --- a/opendaylight/md-sal/statistics-manager/src/main/java/org/opendaylight/controller/md/statistics/manager/NodeStatisticsHandler.java +++ b/opendaylight/md-sal/statistics-manager/src/main/java/org/opendaylight/controller/md/statistics/manager/NodeStatisticsHandler.java @@ -313,6 +313,9 @@ public final class NodeStatisticsHandler implements AutoCloseable, FlowCapableCo meterStats.close(); queueStats.close(); + //Clean up queued statistics request from scheduler queue + srScheduler.removeRequestsFromSchedulerQueue(this.getNodeRef()); + logger.debug("Statistics handler for {} shut down", targetNodeKey.getId()); } diff --git a/opendaylight/md-sal/statistics-manager/src/main/java/org/opendaylight/controller/md/statistics/manager/StatisticsRequestScheduler.java b/opendaylight/md-sal/statistics-manager/src/main/java/org/opendaylight/controller/md/statistics/manager/StatisticsRequestScheduler.java index 9ebfd6fd62..bea43ef68a 100644 --- a/opendaylight/md-sal/statistics-manager/src/main/java/org/opendaylight/controller/md/statistics/manager/StatisticsRequestScheduler.java +++ b/opendaylight/md-sal/statistics-manager/src/main/java/org/opendaylight/controller/md/statistics/manager/StatisticsRequestScheduler.java @@ -18,6 +18,7 @@ import java.util.concurrent.TimeUnit; import org.opendaylight.controller.md.sal.common.api.TransactionStatus; import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction; import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction.DataTransactionListener; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRef; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -62,6 +63,19 @@ public class StatisticsRequestScheduler implements DataTransactionListener { requestQueue.put(statsRequest, null); } + public void removeRequestsFromSchedulerQueue(NodeRef node){ + AbstractStatsTracker stats = null; + synchronized(requestQueue){ + Iterator> nodesItr = requestQueue.entrySet().iterator(); + while(nodesItr.hasNext()){ + stats = nodesItr.next().getKey(); + if(stats.getNodeRef().equals(node)){ + nodesItr.remove(); + } + } + } + + } public AbstractStatsTracker getNextRequestFromSchedulerQueue(){ //Remove first element AbstractStatsTracker stats = null; @@ -79,10 +93,7 @@ public class StatisticsRequestScheduler implements DataTransactionListener { private void requestStatistics(){ AbstractStatsTracker stats = this.getNextRequestFromSchedulerQueue(); - if(stats != null) { - stats.request(); - stats.increaseRequestCounter(); - } + sendStatsRequest(stats); } @Override public void onStatusUpdated(DataModificationTransaction transaction, TransactionStatus status) { @@ -106,12 +117,19 @@ public class StatisticsRequestScheduler implements DataTransactionListener { break; } } + sendStatsRequest(stats); + } + + private void sendStatsRequest(AbstractStatsTracker stats){ if(stats != null){ - stats.request(); - stats.increaseRequestCounter(); + try{ + stats.request(); + stats.increaseRequestCounter(); + }catch(Exception e){ + srsLogger.warn("Statistics request was not sent successfully. Reason : {}",e.getMessage()); + } } } - public void start(){ timer.schedule(task, 0, REQUEST_MONITOR_INTERVAL); } diff --git a/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/ReplaceEditConfigStrategyTest.java b/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/ReplaceEditConfigStrategyTest.java index 78a2043e20..22a3f10547 100644 --- a/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/ReplaceEditConfigStrategyTest.java +++ b/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/ReplaceEditConfigStrategyTest.java @@ -8,7 +8,17 @@ package org.opendaylight.controller.netconf.confignetconfconnector.operations.editconfig; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + import com.google.common.collect.Sets; +import java.util.Map; +import javax.management.Attribute; +import javax.management.ObjectName; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -16,17 +26,6 @@ import org.mockito.MockitoAnnotations; import org.opendaylight.controller.config.util.ConfigTransactionClient; import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.fromxml.AttributeConfigElement; -import javax.management.Attribute; -import javax.management.ObjectName; -import java.util.Map; - -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - public class ReplaceEditConfigStrategyTest { @Mock @@ -35,7 +34,7 @@ public class ReplaceEditConfigStrategyTest { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - doNothing().when(ta).destroyConfigBean(anyString(), anyString()); + doNothing().when(ta).destroyModule(anyString(), anyString()); doReturn(mockON()).when(ta).lookupConfigBean(anyString(), anyString()); doNothing().when(ta).setAttribute(any(ObjectName.class), anyString(), any(Attribute.class)); }