--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>config-plugin-parent</artifactId>
+ <version>0.2.5-SNAPSHOT</version>
+ <relativePath>../config-plugin-parent</relativePath>
+ </parent>
+ <artifactId>logback-config-loader</artifactId>
+ <packaging>bundle</packaging>
+ <name>${project.artifactId}</name>
+ <prerequisites>
+ <maven>3.0.4</maven>
+ </prerequisites>
+
+ <dependencies>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+
+ <!-- test dependencies -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Bundle-Activator>org.opendaylight.controller.logback.config.loader.Activator</Bundle-Activator>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
--- /dev/null
+/**
+ * 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<File> 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?
+ }
+
+}
--- /dev/null
+/**
+ * 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<File> 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<File> sortedConfigFiles = new ArrayList<File>(configs.length);
+ for (File cfgItem : configs) {
+ LOG.trace("config: {}", cfgItem.toURI());
+ sortedConfigFiles.add(cfgItem);
+ }
+ Collections.sort(sortedConfigFiles);
+
+ return sortedConfigFiles;
+ }
+
+}
--- /dev/null
+/**
+ * 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:
+ * <ol>
+ * <li>reset actual configuration (probably default configuration)</li>
+ * <li>load all given logback config xml files in given order</li>
+ * </ol>
+ */
+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");
+ }
+
+}
--- /dev/null
+/**
+ * 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<File> 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<String> logSnapshot = new ArrayList<>(TestAppender.getLogRecord());
+ for (String logLine : logSnapshot) {
+ LOG.info("\"{}\",", logLine);
+ }
+
+ Assert.assertArrayEquals(expectedLogs, logSnapshot.toArray());
+ }
+}
--- /dev/null
+/**
+ * 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 <E>
+ */
+public class TestAppender<E> implements Appender<E> {
+
+ private boolean started;
+ private Context context;
+ private String name;
+
+ private static List<String> 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<E> newFilter) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void clearAllFilters() {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public List<Filter<E>> 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<String> getLogRecord() {
+ return logRecord;
+ }
+
+}
--- /dev/null
+/**
+ * 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");
+ }
+
+}
--- /dev/null
+/**
+ * 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");
+ }
+
+}
--- /dev/null
+/**
+ * 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");
+ }
+
+}
--- /dev/null
+/**
+ * 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");
+ }
+
+}
--- /dev/null
+/**
+ * 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");
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<configuration debug="true">\r
+\r
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">\r
+ <encoder>\r
+ <pattern>%date{"yyyy-MM-dd HH:mm:ss.SSS z"} [%thread] %-5level %logger{36} - %msg%n</pattern>\r
+ </encoder>\r
+ </appender>\r
+\r
+ <root level="INFO">\r
+ <appender-ref ref="STDOUT" />\r
+ </root>\r
+\r
+ <!-- Base log level -->\r
+ <logger name="org.opendaylight.controller.logback.config.loader" level="DEBUG"/>\r
+\r
+</configuration>\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<configuration debug="false">\r
+\r
+ <appender name="TEST" class="org.opendaylight.controller.logback.config.loader.test.TestAppender"/>\r
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">\r
+ <encoder>\r
+ <pattern>%date{"yyyy-MM-dd HH:mm:ss.SSS z"} [%thread] %-5level %logger{36} - %msg%n</pattern>\r
+ </encoder>\r
+ </appender>\r
+\r
+ <root level="INFO">\r
+ <appender-ref ref="TEST" />\r
+ <appender-ref ref="STDOUT" />\r
+ </root>\r
+\r
+ <!-- Base log level -->\r
+ <logger name="org.opendaylight.controller.logback.config.loader" level="INFO"/>\r
+\r
+</configuration>\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<configuration debug="false">\r
+\r
+ <!-- Base log level -->\r
+ <logger name="org.opendaylight.controller.logback.config.loader" level="DEBUG"/>\r
+ <logger name="org.opendaylight.controller.logback.config.loader.test.logwork.Tracer" level="TRACE"/>\r
+<!-- <logger name="org.opendaylight.controller.logback.config.loader.test.logwork.Debugger" level="DEBUG"/> -->\r
+ <logger name="org.opendaylight.controller.logback.config.loader.test.logwork.Informer" level="DEBUG"/>\r
+ <logger name="org.opendaylight.controller.logback.config.loader.test.logwork.Warner" level="ERROR"/>\r
+ <logger name="org.opendaylight.controller.logback.config.loader.test.logwork.Errorer" level="ERROR"/>\r
+\r
+</configuration>\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<configuration debug="false">\r
+ <root level="INFO">\r
+ <appender-ref ref="TEST" />\r
+ </root>\r
+\r
+ <logger name="org.opendaylight.controller.logback.config.loader.test.logwork.Informer" level="INFO"/>\r
+ <logger name="org.opendaylight.controller.logback.config.loader.test.logwork.Warner" level="WARN"/>\r
+\r
+ <logger name="org.opendaylight.controller.logback.config.loader.test.LogbackConfigurationLoaderTest" level="TRACE"/>\r
+</configuration>\r
*/
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.common.collect.Lists;
-
public final class NetconfDeviceSalFacade implements AutoCloseable, RemoteDeviceHandler<NetconfSessionCapabilities> {
- private static final Logger logger= LoggerFactory.getLogger(NetconfDeviceTwoPhaseCommitTransaction.class);
+ private static final Logger logger= LoggerFactory.getLogger(NetconfDeviceSalFacade.class);
private static final InstanceIdentifier ROOT_PATH = InstanceIdentifier.builder().toInstance();
private final RemoteDeviceId id;
}
if (failedRpcs.isEmpty() == false) {
- logger.warn("{}: Some rpcs from netconf device were not registered: {}", id, failedRpcs);
+ 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());
+ }
}
}
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");
}
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;
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()) {
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()) {
*/
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;
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;
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();
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);
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) {
@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<TransactionStatus> status = null;
@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(
@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(
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;
}
+ @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 <b>stay unchanged</b> if concrete values are
* present in composite or simple node and if the method for update is
ListenableFuture<RpcResult<CompositeNode>> 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 );
mockCommitConfigurationDataPostMethod(TransactionStatus.FAILED);
assertEquals(500, post(uri, MediaType.APPLICATION_XML, xmlDataInterfaceAbsolutePath));
+
+ assertEquals( 400, post(uri, MediaType.APPLICATION_JSON, "" ));
}
@Test
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) {
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;
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;
resourceConfig = resourceConfig.registerInstances(restconfImpl, StructuredDataToXmlProvider.INSTANCE,
StructuredDataToJsonProvider.INSTANCE, XmlToCompositeNodeProvider.INSTANCE,
JsonToCompositeNodeProvider.INSTANCE);
+ resourceConfig.registerClasses( RestconfDocumentedExceptionMapper.class );
return resourceConfig;
}
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
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;
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 );
+ }
+
}