From: Maros Marsalek Date: Tue, 8 Oct 2013 16:26:35 +0000 (+0200) Subject: Initial code drop of netconf protocol implementation X-Git-Tag: jenkins-controller-bulk-release-prepare-only-2-1~631 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=a92d9d6a21a0f6ca8d2153795721f500eaf29ee9 Initial code drop of netconf protocol implementation This implementation is based on infrastructure provided by the framework artifact and contains server as well as client side code. Netconf subsystem structure: Netconf-api: Api definition for client and server. Netconf-impl: Netconf server implementation. Server handles basic communication and delegates handling of rpcs to an implementation of netconf-mapping-api. Netconf-mapping-api: Api definition for pluggable rpcs handler. Implementations of this api are plugged dynamically into netconf-impl using OSGi apis. Config-netconf-connector: Implementation of netconf-mapping-api that handles netconf rpcs and delegates requests to configuration subsystem Netconf-util: Utility classes used by client and server code. Netconf-client: Netconf client implementation. Netconf-it: Integration tests for netconf. These tests verify correct cooperation of netconf server, config-netconf-connector, config subsystem and netconf client. Config-persister-api: Api definition for config persister. Config persister is a component that is responsible for storing configuration snapshots after every change to the configuration via netconf rpcs. (Pushed to config-subsystem) Config-persister-impl: Implementation of config persister that receives notifications from netconf-impl about changes to the configuration and uses pluggable adapters to store received snapshots. Config-persister-file-adapter: Implementation of config persister adapter that stores and restores config snapshots from a file. (Pushed to config-subsystem) In order to run netconf in OSGi, some configuration issues had to be resolved in config-subsystem bundles. Change-Id: I8e0421c924b0714a4d49962c4bb5ca01ef68ac78 Signed-off-by: Maros Marsalek --- diff --git a/opendaylight/commons/opendaylight/pom.xml b/opendaylight/commons/opendaylight/pom.xml index 223abcd114..296250d945 100644 --- a/opendaylight/commons/opendaylight/pom.xml +++ b/opendaylight/commons/opendaylight/pom.xml @@ -54,7 +54,7 @@ 2.2.0.RELEASE 2.10 -Xmx1024m -XX:MaxPermSize=256m - 0.5.8 + 0.5.9-SNAPSHOT 14.0.1 5.0.0 2010.09.24.1 @@ -70,7 +70,8 @@ 2.3.7 4.8.1 0.2.0-SNAPSHOT - 0.5.8 + 0.5.9-SNAPSHOT + 0.6.0-SNAPSHOT 0.4.1-SNAPSHOT 0.2.1-SNAPSHOT @@ -634,11 +635,44 @@ ${bgpcep.version} + + org.opendaylight.bgpcep + framework + ${bgpcep.version} + + + + + io.netty + netty-handler + 4.0.9.Final + + + io.netty + netty-codec + 4.0.9.Final + + + io.netty + netty-buffer + 4.0.9.Final + + + io.netty + netty-transport + 4.0.9.Final + + + io.netty + netty-common + 4.0.9.Final + + org.opendaylight.yangtools yang-binding - ${yangtools.version} + ${yangtools.binding.version} org.opendaylight.yangtools diff --git a/opendaylight/config/config-api/pom.xml b/opendaylight/config/config-api/pom.xml index 858a8020d0..4dbc31f062 100644 --- a/opendaylight/config/config-api/pom.xml +++ b/opendaylight/config/config-api/pom.xml @@ -23,6 +23,10 @@ concepts 0.2.0-SNAPSHOT + + org.osgi + org.osgi.core + @@ -34,7 +38,8 @@ javax.management, - org.opendaylight.protocol.concepts + org.opendaylight.protocol.concepts, + org.osgi.framework, org.opendaylight.controller.config.api, @@ -43,6 +48,7 @@ org.opendaylight.controller.config.api.jmx, org.opendaylight.controller.config.api.jmx.constants, org.opendaylight.controller.config.api.runtime, + org.opendaylight.controller.config.stat, diff --git a/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/stat/ConfigProvider.java b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/stat/ConfigProvider.java new file mode 100644 index 0000000000..3a81061881 --- /dev/null +++ b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/stat/ConfigProvider.java @@ -0,0 +1,55 @@ +/* + * 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.config.stat; + +import org.osgi.framework.BundleContext; + +/** + * Subset of {@link org.osgi.framework.BundleContext} + */ +public interface ConfigProvider { + /** + * Returns the value of the specified property. If the key is not found in + * the Framework properties, the system properties are then searched. The + * method returns {@code null} if the property is not found. + * + *

+ * All bundles must have permission to read properties whose names start + * with "org.osgi.". + * + * @param key + * The name of the requested property. + * @return The value of the requested property, or {@code null} if the + * property is undefined. + * @throws SecurityException + * If the caller does not have the appropriate + * {@code PropertyPermission} to read the property, and the Java + * Runtime Environment supports permissions. + */ + String getProperty(String key); + + public static class ConfigProviderImpl implements ConfigProvider { + private final BundleContext context; + + public ConfigProviderImpl(BundleContext context) { + this.context = context; + } + + @Override + public String getProperty(String key) { + return context.getProperty(key); + } + + @Override + public String toString() { + return "ConfigProviderImpl{" + "context=" + context + '}'; + } + } + +} diff --git a/opendaylight/config/config-persister-api/pom.xml b/opendaylight/config/config-persister-api/pom.xml new file mode 100644 index 0000000000..867c12c18c --- /dev/null +++ b/opendaylight/config/config-persister-api/pom.xml @@ -0,0 +1,48 @@ + + 4.0.0 + + config-subsystem + org.opendaylight.controller + 0.2.1-SNAPSHOT + .. + + config-persister-api + ${project.artifactId} + bundle + + + + ${project.groupId} + config-util + 0.2.1-SNAPSHOT + + + com.google.guava + guava + + + + + + + org.apache.felix + maven-bundle-plugin + + + + com.google.common.base, + org.w3c.dom, + org.osgi.framework, + org.opendaylight.controller.config.stat + + + org.opendaylight.controller.config.persist.api, + org.opendaylight.controller.config.persist.api.storage, + + + + + + + diff --git a/opendaylight/config/config-persister-api/src/main/java/org/opendaylight/controller/config/persist/api/Persister.java b/opendaylight/config/config-persister-api/src/main/java/org/opendaylight/controller/config/persist/api/Persister.java new file mode 100644 index 0000000000..f9c9301b88 --- /dev/null +++ b/opendaylight/config/config-persister-api/src/main/java/org/opendaylight/controller/config/persist/api/Persister.java @@ -0,0 +1,32 @@ +/* + * 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.config.persist.api; + +import com.google.common.base.Optional; + +import java.io.Closeable; +import java.io.IOException; +import java.util.Set; + +/** + * Base interface for persister implementation. + */ +public interface Persister extends Closeable { + + void persistConfig(ConfigSnapshotHolder configSnapshotHolder) throws IOException; + + Optional loadLastConfig() throws IOException; + + public static interface ConfigSnapshotHolder { + + String getConfigSnapshot(); + + Set getCapabilities(); + } +} diff --git a/opendaylight/config/config-persister-api/src/main/java/org/opendaylight/controller/config/persist/api/storage/StorageAdapter.java b/opendaylight/config/config-persister-api/src/main/java/org/opendaylight/controller/config/persist/api/storage/StorageAdapter.java new file mode 100644 index 0000000000..447504027e --- /dev/null +++ b/opendaylight/config/config-persister-api/src/main/java/org/opendaylight/controller/config/persist/api/storage/StorageAdapter.java @@ -0,0 +1,22 @@ +/* + * 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.config.persist.api.storage; + +import org.opendaylight.controller.config.persist.api.Persister; +import org.opendaylight.controller.config.stat.ConfigProvider; + +/** + * Plugins for {@link org.opendaylight.controller.config.persist.api.Persister} + * must implement this interface. + */ +public interface StorageAdapter extends Persister { + + void setProperties(ConfigProvider configProvider); + +} diff --git a/opendaylight/config/config-persister-file-adapter/pom.xml b/opendaylight/config/config-persister-file-adapter/pom.xml new file mode 100644 index 0000000000..51fcf8641a --- /dev/null +++ b/opendaylight/config/config-persister-file-adapter/pom.xml @@ -0,0 +1,97 @@ + + + 4.0.0 + + config-subsystem + org.opendaylight.controller + 0.2.1-SNAPSHOT + .. + + config-persister-file-adapter + ${project.artifactId} + bundle + + + + + ${project.groupId} + config-persister-api + ${project.version} + + + org.apache.commons + commons-lang3 + + + com.google.guava + guava + + + org.slf4j + slf4j-api + + + + + org.opendaylight.bgpcep + mockito-configuration + 0.2.0-SNAPSHOT + test + + + + + + + + org.codehaus.groovy.maven + gmaven-plugin + + + generate-sources + + execute + + + + System.setProperty("osgiversion", "${project.version}".replace('-', '.')) + + + + + + + org.apache.felix + maven-bundle-plugin + + + ${project.groupId}.config-persister-impl;bundle-version=${osgiversion} + + org.opendaylight.controller.config.persister.storage.adapter + + + org.osgi.framework, + com.google.common.base, + com.google.common.collect, + com.google.common.io, + javax.xml.parsers, + javax.xml.transform, + javax.xml.transform.dom, + javax.xml.transform.stream, + org.apache.commons.lang3, + org.opendaylight.controller.config.persist.api, + org.opendaylight.controller.config.stat, + org.opendaylight.controller.config.persist.api.storage, + org.slf4j, + org.w3c.dom, + org.xml.sax, + + + + + + + + diff --git a/opendaylight/config/config-persister-file-adapter/src/main/java/org/opendaylight/controller/config/persist/storage/file/FileStorageAdapter.java b/opendaylight/config/config-persister-file-adapter/src/main/java/org/opendaylight/controller/config/persist/storage/file/FileStorageAdapter.java new file mode 100644 index 0000000000..a866743b0d --- /dev/null +++ b/opendaylight/config/config-persister-file-adapter/src/main/java/org/opendaylight/controller/config/persist/storage/file/FileStorageAdapter.java @@ -0,0 +1,273 @@ +/* + * 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.config.persist.storage.file; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Charsets; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.Sets; +import com.google.common.io.Files; +import org.apache.commons.lang3.StringUtils; +import org.opendaylight.controller.config.persist.api.storage.StorageAdapter; +import org.opendaylight.controller.config.stat.ConfigProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.SAXException; + +import javax.xml.parsers.ParserConfigurationException; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.Set; + +/** + * StorageAdapter that stores configuration in a plan file. + */ +public class FileStorageAdapter implements StorageAdapter { + private static final Logger logger = LoggerFactory.getLogger(FileStorageAdapter.class); + + // TODO prefix properties + + private static final Charset ENCODING = Charsets.UTF_8; + + public static final String FILE_STORAGE_PROP = "fileStorage"; + public static final String NUMBER_OF_BACKUPS = "numberOfBackups"; + + private static final String SEPARATOR_E_PURE = "//END OF CONFIG"; + private static final String SEPARATOR_E = newLine(SEPARATOR_E_PURE); + + private static final String SEPARATOR_M_PURE = "//END OF SNAPSHOT"; + private static final String SEPARATOR_M = newLine(SEPARATOR_M_PURE); + + private static final String SEPARATOR_S = newLine("//START OF CONFIG"); + + private static final String SEPARATOR_SL_PURE = "//START OF CONFIG-LAST"; + private static final String SEPARATOR_SL = newLine(SEPARATOR_SL_PURE); + + private static Integer numberOfStoredBackups; + private File storage; + + @Override + public void setProperties(ConfigProvider configProvider) { + File storage = extractStorageFileFromProperties(configProvider); + logger.debug("Using file {}", storage.getAbsolutePath()); + // Create file if it does not exist + File parentFile = storage.getAbsoluteFile().getParentFile(); + if (parentFile.exists() == false) { + logger.debug("Creating parent folders {}", parentFile); + parentFile.mkdirs(); + } + if (storage.exists() == false) { + logger.debug("Storage file does not exist, creating empty file"); + try { + boolean result = storage.createNewFile(); + if (result == false) + throw new RuntimeException("Unable to create storage file " + storage); + } catch (IOException e) { + throw new RuntimeException("Unable to create storage file " + storage, e); + } + } + if (numberOfStoredBackups == 0) { + throw new RuntimeException(NUMBER_OF_BACKUPS + + " property should be either set to positive value, or ommited. Can not be set to 0."); + } + setFileStorage(storage); + + } + + @VisibleForTesting + void setFileStorage(File storage) { + this.storage = storage; + } + + @VisibleForTesting + void setNumberOfBackups(Integer numberOfBackups) { + numberOfStoredBackups = numberOfBackups; + } + + private static File extractStorageFileFromProperties(ConfigProvider configProvider) { + String fileStorageProperty = configProvider.getProperty(FILE_STORAGE_PROP); + Preconditions.checkNotNull(fileStorageProperty, "Unable to find " + FILE_STORAGE_PROP + + " in received properties :" + configProvider); + File result = new File(fileStorageProperty); + String numberOfBAckupsAsString = configProvider.getProperty(NUMBER_OF_BACKUPS); + if (numberOfBAckupsAsString != null) { + numberOfStoredBackups = Integer.valueOf(numberOfBAckupsAsString); + } else { + numberOfStoredBackups = Integer.MAX_VALUE; + } + + return result; + } + + private static String newLine(String string) { + return string + "\n"; + } + + @Override + public void persistConfig(ConfigSnapshotHolder holder) throws IOException { + Preconditions.checkNotNull(storage, "Storage file is null"); + + String content = Files.toString(storage, ENCODING); + if (numberOfStoredBackups == Integer.MAX_VALUE) { + resetLastConfig(content); + persistLastConfig(holder); + } else { + if (numberOfStoredBackups == 1) { + Files.write("", storage, ENCODING); + persistLastConfig(holder); + } else { + int count = StringUtils.countMatches(content, SEPARATOR_S); + if ((count + 1) < numberOfStoredBackups) { + resetLastConfig(content); + persistLastConfig(holder); + } else { + String contentSubString = StringUtils.substringBefore(content, SEPARATOR_E); + contentSubString = contentSubString.concat(SEPARATOR_E_PURE); + content = StringUtils.substringAfter(content, contentSubString); + resetLastConfig(content); + persistLastConfig(holder); + } + } + } + } + + private void resetLastConfig(String content) throws IOException { + content = content.replaceFirst(SEPARATOR_SL, SEPARATOR_S); + Files.write(content, storage, ENCODING); + } + + private void persistLastConfig(ConfigSnapshotHolder holder) throws IOException { + Files.append(SEPARATOR_SL, storage, ENCODING); + String snapshotAsString = holder.getConfigSnapshot(); + Files.append(newLine(snapshotAsString), storage, ENCODING); + Files.append(SEPARATOR_M, storage, ENCODING); + Files.append(toStringCaps(holder.getCapabilities()), storage, ENCODING); + Files.append(SEPARATOR_E, storage, ENCODING); + } + + private CharSequence toStringCaps(Set capabilities) { + StringBuilder b = new StringBuilder(); + for (String capability : capabilities) { + b.append(newLine(capability)); + } + return b.toString(); + } + + @Override + public Optional loadLastConfig() throws IOException { + Preconditions.checkNotNull(storage, "Storage file is null"); + + if (!storage.exists()) { + return Optional.absent(); + } + + final LineProcessor lineProcessor = new LineProcessor(); + String result = Files.readLines(storage, ENCODING, lineProcessor); + + try { + if (lineProcessor.getConfigSnapshot().isPresent() == false) { + return Optional.absent(); + } else { + return Optional. of(new PersistedConfigImpl(lineProcessor.getConfigSnapshot(), + lineProcessor.getCapabilities())); + } + + } catch (ParserConfigurationException | SAXException e) { + throw new IOException("Unable to load last config ", e); + } + } + + private static final class LineProcessor implements com.google.common.io.LineProcessor { + + private boolean inLastConfig, inLastSnapshot; + private final StringBuffer snapshotBuffer = new StringBuffer(); + private final Set caps = Sets.newHashSet(); + + @Override + public String getResult() { + return null; + } + + @Override + public boolean processLine(String line) throws IOException { + if (inLastConfig && line.equals(SEPARATOR_E_PURE)) { + inLastConfig = false; + return false; + } + + if (inLastConfig && line.equals(SEPARATOR_M_PURE)) { + inLastSnapshot = false; + return true; + } + + if (inLastConfig) { + if (inLastSnapshot) { + snapshotBuffer.append(line); + snapshotBuffer.append(System.lineSeparator()); + } else { + caps.add(line); + } + } + + if (line.equals(SEPARATOR_SL_PURE)) { + inLastConfig = true; + inLastSnapshot = true; + } + + return true; + } + + Optional getConfigSnapshot() throws IOException, SAXException, ParserConfigurationException { + final String xmlContent = snapshotBuffer.toString(); + if (xmlContent == null || xmlContent.equals("")) { + return Optional.absent(); + } else + return Optional.of(xmlContent); + } + + Set getCapabilities() throws IOException, SAXException, ParserConfigurationException { + return caps; + } + + } + + @Override + public void close() throws IOException { + + } + + @Override + public String toString() { + return "FileStorageAdapter [storage=" + storage + "]"; + } + + private class PersistedConfigImpl implements ConfigSnapshotHolder { + + private final String snapshot; + private final Set caps; + + public PersistedConfigImpl(Optional configSnapshot, Set capabilities) { + this.snapshot = configSnapshot.get(); + this.caps = capabilities; + } + + @Override + public String getConfigSnapshot() { + return snapshot; + } + + @Override + public Set getCapabilities() { + return caps; + } + } + +} diff --git a/opendaylight/config/config-persister-file-adapter/src/test/java/org/opendaylight/controller/config/persist/storage/file/FileStorageAdapterTest.java b/opendaylight/config/config-persister-file-adapter/src/test/java/org/opendaylight/controller/config/persist/storage/file/FileStorageAdapterTest.java new file mode 100644 index 0000000000..acb44ba441 --- /dev/null +++ b/opendaylight/config/config-persister-file-adapter/src/test/java/org/opendaylight/controller/config/persist/storage/file/FileStorageAdapterTest.java @@ -0,0 +1,209 @@ +/* + * 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.config.persist.storage.file; + +import com.google.common.base.Charsets; +import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.common.collect.Collections2; +import com.google.common.collect.Sets; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.opendaylight.controller.config.persist.api.Persister; + +import java.io.File; +import java.nio.file.Files; +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.*; + +public class FileStorageAdapterTest { + + private static int i; + private File file; + + @Before + public void setUp() throws Exception { + file = Files.createTempFile("testFilePersist", ".txt").toFile(); + if (!file.exists()) + return; + com.google.common.io.Files.write("", file, Charsets.UTF_8); + i = 1; + } + + @Test + public void testFileAdapter() throws Exception { + FileStorageAdapter storage = new FileStorageAdapter(); + storage.setFileStorage(file); + storage.setNumberOfBackups(Integer.MAX_VALUE); + final Persister.ConfigSnapshotHolder holder = new Persister.ConfigSnapshotHolder() { + @Override + public String getConfigSnapshot() { + return createConfig(); + } + + @Override + public Set getCapabilities() { + return createCaps(); + } + }; + storage.persistConfig(holder); + + storage.persistConfig(holder); + + Collection readLines = Collections2.filter(com.google.common.io.Files.readLines(file, Charsets.UTF_8), + new Predicate() { + + @Override + public boolean apply(String input) { + if (input.equals("")) + return false; + return true; + } + }); + assertEquals(14, readLines.size()); + + Optional lastConf = storage.loadLastConfig(); + assertTrue(lastConf.isPresent()); + assertEquals("2", + lastConf.get().getConfigSnapshot().replaceAll("\\s", "")); + assertEquals(createCaps(), lastConf.get().getCapabilities()); + } + + private Set createCaps() { + Set caps = Sets.newHashSet(); + + caps.add("cap1"); + caps.add("cap2"); + caps.add("capaaaa as dasfasdf s2"); + return caps; + } + + @Test + public void testFileAdapterOneBackup() throws Exception { + FileStorageAdapter storage = new FileStorageAdapter(); + storage.setFileStorage(file); + storage.setNumberOfBackups(1); + final Persister.ConfigSnapshotHolder holder = new Persister.ConfigSnapshotHolder() { + @Override + public String getConfigSnapshot() { + return createConfig(); + } + + @Override + public Set getCapabilities() { + return createCaps(); + } + }; + storage.persistConfig(holder); + + storage.persistConfig(holder); + + Collection readLines = Collections2.filter(com.google.common.io.Files.readLines(file, Charsets.UTF_8), + new Predicate() { + + @Override + public boolean apply(String input) { + if (input.equals("")) + return false; + return true; + } + }); + assertEquals(7, readLines.size()); + + Optional lastConf = storage.loadLastConfig(); + assertTrue(lastConf.isPresent()); + assertEquals("2", + lastConf.get().getConfigSnapshot().replaceAll("\\s", "")); + } + + @Test + public void testFileAdapterOnlyTwoBackups() throws Exception { + FileStorageAdapter storage = new FileStorageAdapter(); + storage.setFileStorage(file); + storage.setNumberOfBackups(2); + final Persister.ConfigSnapshotHolder holder = new Persister.ConfigSnapshotHolder() { + @Override + public String getConfigSnapshot() { + return createConfig(); + } + + @Override + public Set getCapabilities() { + return createCaps(); + } + }; + storage.persistConfig(holder); + + storage.persistConfig(holder); + storage.persistConfig(holder); + + Collection readLines = Collections2.filter(com.google.common.io.Files.readLines(file, Charsets.UTF_8), + new Predicate() { + + @Override + public boolean apply(String input) { + if (input.equals("")) + return false; + return true; + } + }); + + assertEquals(14, readLines.size()); + + Optional lastConf = storage.loadLastConfig(); + assertTrue(lastConf.isPresent()); + assertEquals("3", + lastConf.get().getConfigSnapshot().replaceAll("\\s", "")); + assertFalse(readLines.contains(holder.getConfigSnapshot())); + } + + @Test + public void testNoLastConfig() throws Exception { + File file = Files.createTempFile("testFilePersist", ".txt").toFile(); + if (!file.exists()) + return; + FileStorageAdapter storage = new FileStorageAdapter(); + storage.setFileStorage(file); + + Optional elementOptional = storage.loadLastConfig(); + assertThat(elementOptional.isPresent(), is(false)); + } + + @Test(expected = NullPointerException.class) + public void testNoProperties() throws Exception { + FileStorageAdapter storage = new FileStorageAdapter(); + storage.loadLastConfig(); + } + + @Test(expected = NullPointerException.class) + public void testNoProperties2() throws Exception { + FileStorageAdapter storage = new FileStorageAdapter(); + storage.persistConfig(new Persister.ConfigSnapshotHolder() { + @Override + public String getConfigSnapshot() { + return Mockito.mock(String.class); + } + + @Override + public Set getCapabilities() { + return Collections. emptySet(); + } + } ); + } + + static String createConfig() { + return "" + i++ + ""; + } + +} diff --git a/opendaylight/config/pom.xml b/opendaylight/config/pom.xml index 6484c30274..1812fbb449 100755 --- a/opendaylight/config/pom.xml +++ b/opendaylight/config/pom.xml @@ -22,6 +22,8 @@ config-api config-manager config-util + config-persister-api + config-persister-file-adapter yang-jmx-generator yang-jmx-generator-plugin yang-jmx-generator-it @@ -40,7 +42,6 @@ 0.6.2.201302030002 1.7.2 1.1.1 - 5.0.0 0.5.9-SNAPSHOT 0.6.0-SNAPSHOT ${project.build.directory}/generated-sources/config @@ -270,6 +271,39 @@ gmaven-plugin 1.0 + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + + org.opendaylight.yangtools + + + yang-maven-plugin + + + [0.5.7-SNAPSHOT,) + + + + generate-sources + + + + + + + + + + + @@ -277,7 +311,7 @@ nexus.opendaylight.org - http://nexus.opendaylight.org/content/repositories/opendaylight.snapshot + http://nexus.opendaylight.org/content/repositories/opendaylight.release true diff --git a/opendaylight/config/yang-jmx-generator-plugin/pom.xml b/opendaylight/config/yang-jmx-generator-plugin/pom.xml index 1079ce9fd7..81d55efeaf 100644 --- a/opendaylight/config/yang-jmx-generator-plugin/pom.xml +++ b/opendaylight/config/yang-jmx-generator-plugin/pom.xml @@ -9,51 +9,61 @@ yang-jmx-generator-plugin + org.slf4j slf4j-api + org.opendaylight.controller yang-jmx-generator 0.2.1-SNAPSHOT + org.opendaylight.yangtools yang-maven-plugin-spi ${opendaylight.yang.version} + org.opendaylight.yangtools - binding-generator-impl + binding-type-provider ${opendaylight.binding.version} + org.eclipse.jdt core 3.3.0-v_771 test + org.freemarker freemarker 2.3.20 + ${project.groupId} config-api 0.2.1-SNAPSHOT + commons-io commons-io + com.googlecode.slf4j-maven-plugin-log slf4j-maven-plugin-log 1.0.0 + com.google.guava guava @@ -66,21 +76,24 @@ test test-jar + org.eclipse jdt 3.3.0-v20070607-1300 test + org.opendaylight.bgpcep mockito-configuration 0.2.0-SNAPSHOT test + - org.apache.commons - commons-lang3 + org.apache.commons + commons-lang3 diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/JMXGenerator.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/JMXGenerator.java index 743ffba739..e40ca8b18b 100644 --- a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/JMXGenerator.java +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/JMXGenerator.java @@ -7,17 +7,11 @@ */ package org.opendaylight.controller.config.yangjmxgenerator.plugin; -import java.io.File; -import java.io.IOException; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; import org.apache.commons.io.FileUtils; import org.apache.maven.plugin.logging.Log; import org.apache.maven.project.MavenProject; @@ -35,11 +29,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.impl.StaticLoggerBinder; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Preconditions; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * This class interfaces with yang-maven-plugin. Gets parsed yang modules in diff --git a/opendaylight/config/yang-jmx-generator/pom.xml b/opendaylight/config/yang-jmx-generator/pom.xml index dce382ddb7..67a1c80b9c 100644 --- a/opendaylight/config/yang-jmx-generator/pom.xml +++ b/opendaylight/config/yang-jmx-generator/pom.xml @@ -18,16 +18,19 @@ org.slf4j slf4j-api + org.opendaylight.yangtools - binding-generator-util + binding-generator-spi ${opendaylight.binding.version} org.opendaylight.yangtools - binding-generator-spi + binding-generator-util ${opendaylight.binding.version} + + com.google.guava guava diff --git a/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/RuntimeBeanEntry.java b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/RuntimeBeanEntry.java index 6d1eca1193..d07e20bebb 100644 --- a/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/RuntimeBeanEntry.java +++ b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/RuntimeBeanEntry.java @@ -7,20 +7,9 @@ */ package org.opendaylight.controller.config.yangjmxgenerator; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkState; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Deque; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; - +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Optional; +import com.google.common.collect.Lists; import org.opendaylight.controller.config.yangjmxgenerator.attribute.AttributeIfc; import org.opendaylight.controller.config.yangjmxgenerator.attribute.JavaAttribute; import org.opendaylight.controller.config.yangjmxgenerator.attribute.ListAttribute; @@ -28,22 +17,12 @@ import org.opendaylight.controller.config.yangjmxgenerator.attribute.TOAttribute import org.opendaylight.controller.config.yangjmxgenerator.plugin.util.FullyQualifiedNameHelper; import org.opendaylight.controller.config.yangjmxgenerator.plugin.util.NameConflictException; import org.opendaylight.yangtools.yang.common.QName; -import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode; -import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; -import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; -import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; -import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode; -import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; -import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; -import org.opendaylight.yangtools.yang.model.api.Module; -import org.opendaylight.yangtools.yang.model.api.RpcDefinition; -import org.opendaylight.yangtools.yang.model.api.SchemaNode; -import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode; -import org.opendaylight.yangtools.yang.model.api.UsesNode; +import org.opendaylight.yangtools.yang.model.api.*; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Optional; -import com.google.common.collect.Lists; +import java.util.*; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; /** * Holds information about runtime bean to be generated. There are two kinds of @@ -295,8 +274,8 @@ public class RuntimeBeanEntry { + "Error occured in " + rpcDefinition); } List parameters = new ArrayList<>(); - for (DataSchemaNode childNode : rpcDefinition.getInput() - .getChildNodes()) { + for (DataSchemaNode childNode : sortAttributes(rpcDefinition.getInput() + .getChildNodes())) { if (childNode.isAddedByUses() == false) { // skip // refined // context-instance @@ -318,6 +297,17 @@ public class RuntimeBeanEntry { attributes, rpcs); } + private static Collection sortAttributes(Set childNodes) { + final TreeSet dataSchemaNodes = new TreeSet<>(new Comparator() { + @Override + public int compare(DataSchemaNode o1, DataSchemaNode o2) { + return o1.getQName().getLocalName().compareTo(o2.getQName().getLocalName()); + } + }); + dataSchemaNodes.addAll(childNodes); + return dataSchemaNodes; + } + private static boolean isInnerStateBean(DataSchemaNode child) { for (UnknownSchemaNode unknownSchemaNode : child .getUnknownSchemaNodes()) { diff --git a/opendaylight/config/yang-store-impl/pom.xml b/opendaylight/config/yang-store-impl/pom.xml index 69531c61b6..97285c1f22 100644 --- a/opendaylight/config/yang-store-impl/pom.xml +++ b/opendaylight/config/yang-store-impl/pom.xml @@ -32,7 +32,7 @@ org.opendaylight.yangtools - binding-generator-impl + binding-type-provider ${opendaylight.binding.version} diff --git a/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/chunked1.txt b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/chunked1.txt new file mode 100644 index 0000000000..aad72393cb --- /dev/null +++ b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/chunked1.txt @@ -0,0 +1,35 @@ + +#24 + + + 14 + fred + + < +#2 +/r +#3 +pc> +## diff --git a/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/chunked2.txt b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/chunked2.txt new file mode 100644 index 0000000000..a36a85ea1f --- /dev/null +++ b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/chunked2.txt @@ -0,0 +1,48 @@ + +#22 + + + + + +#18 + + + +#19 + + +#8 + + +#77 + + + + + +## diff --git a/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/chunked3.txt b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/chunked3.txt new file mode 100644 index 0000000000..d9dc43d620 --- /dev/null +++ b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/chunked3.txt @@ -0,0 +1,43 @@ + +#43 + + +#26 + + + +#35 + + + +#39 + + < +#40 +top xmlns="http://example.com/schema/1.2 +#26 +/config"> + + +#36 + + f +#56 +red + + + +#28 + + + + +## diff --git a/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/chunked4.txt b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/chunked4.txt new file mode 100644 index 0000000000..0b8a102dff --- /dev/null +++ b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/chunked4.txt @@ -0,0 +1,40 @@ + +#17 + + + + + + +#43 + + + +#16 + + +#22 + + + +## diff --git a/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/chunked5.txt b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/chunked5.txt new file mode 100644 index 0000000000..f8f3c4d7c2 --- /dev/null +++ b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/chunked5.txt @@ -0,0 +1,42 @@ + +#43 + + + + + < +#4 +/tar +#18 +get> + + +#41 + + + +#29 + Ethernet0/0 + 1500 + +#61 + + + + + +## diff --git a/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/databaseinteractions/jolokia_config_bean_response.txt b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/databaseinteractions/jolokia_config_bean_response.txt new file mode 100644 index 0000000000..2ae32efb65 --- /dev/null +++ b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/databaseinteractions/jolokia_config_bean_response.txt @@ -0,0 +1,18 @@ +curl http://localhost:17777/jolokia/read/org.opendaylight.controller:instanceName=fixed1,type=ConfigBean,interfaceName=testing-threadpool | jsonpp +{ + "request": { + "mbean": "org.opendaylight.controller:instanceName=fixed1,interfaceName=testing-threadpool,type=ConfigBean", + "type": "read" + }, + "status": 200, + "timestamp": 1362416252, + "value": { + "ExportedInterfaces": [ + "testing-threadpool", + "modifiable-threadpool" + ], + "ImplementationName": "fixed", + "ThreadCount": 10, + "TriggerNewInstanceCreation": false + } +} \ No newline at end of file diff --git a/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/databaseinteractions/jolokia_lookupConfigBeans.txt b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/databaseinteractions/jolokia_lookupConfigBeans.txt new file mode 100644 index 0000000000..2ae705a54f --- /dev/null +++ b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/databaseinteractions/jolokia_lookupConfigBeans.txt @@ -0,0 +1,18 @@ +$ curl 'http://localhost:17777/jolokia/exec/org.opendaylight.controller:type=ConfigRegistry/lookupConfigBeans()' | jsonpp +{ + "request": { + "mbean": "org.opendaylight.controller:type=ConfigRegistry", + "operation": "lookupConfigBeans()", + "type": "exec" + }, + "status": 200, + "timestamp": 1362417043, + "value": [ + { + "objectName": "org.opendaylight.controller:instanceName=fixed1,interfaceName=modifiable-threadpool,type=ConfigBean" + }, + { + "objectName": "org.opendaylight.controller:instanceName=fixed1,interfaceName=testing-threadpool,type=ConfigBean" + } + ] +} diff --git a/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/notused/client_commit.xml b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/notused/client_commit.xml new file mode 100644 index 0000000000..6eca609b6c --- /dev/null +++ b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/notused/client_commit.xml @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/notused/client_lock_candidate.xml b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/notused/client_lock_candidate.xml new file mode 100644 index 0000000000..6a9ed639d8 --- /dev/null +++ b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/notused/client_lock_candidate.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/notused/client_lock_running.xml b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/notused/client_lock_running.xml new file mode 100644 index 0000000000..2d66c45906 --- /dev/null +++ b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/notused/client_lock_running.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/notused/client_modify_candidate.xml b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/notused/client_modify_candidate.xml new file mode 100644 index 0000000000..ce67845de1 --- /dev/null +++ b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/notused/client_modify_candidate.xml @@ -0,0 +1,20 @@ + + + + + + none + test-then-set + stop-on-error + + + + 7 + + + + + \ No newline at end of file diff --git a/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/notused/client_unlock_candidate.xml b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/notused/client_unlock_candidate.xml new file mode 100644 index 0000000000..dd6fe1ba1e --- /dev/null +++ b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/notused/client_unlock_candidate.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/notused/client_unlock_running.xml b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/notused/client_unlock_running.xml new file mode 100644 index 0000000000..f94af4698d --- /dev/null +++ b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/notused/client_unlock_running.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/notused/server_error_missing_attribute.xml b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/notused/server_error_missing_attribute.xml new file mode 100644 index 0000000000..c70184e2b4 --- /dev/null +++ b/opendaylight/controller/netconf/netconf-impl/src/test/resources/org/opendaylight/netconf/impl/listener/databaseinteractions/notused/server_error_missing_attribute.xml @@ -0,0 +1,11 @@ + + + rpc + missing-attribute + error + + message-id + rpc + + + diff --git a/opendaylight/distribution/opendaylight/pom.xml b/opendaylight/distribution/opendaylight/pom.xml index a11f93b83d..1b747f6ce8 100644 --- a/opendaylight/distribution/opendaylight/pom.xml +++ b/opendaylight/distribution/opendaylight/pom.xml @@ -121,7 +121,7 @@ 0.1.1-SNAPSHOT - org.opendaylight.controller config-api @@ -172,7 +172,56 @@ logback-config ${config.version} - --> + + org.opendaylight.controller + config-persister-api + ${config.version} + + + org.opendaylight.controller + config-persister-file-adapter + ${config.version} + + + + + + org.opendaylight.controller + netconf-api + ${config.version} + + + org.opendaylight.controller + netconf-impl + ${config.version} + + + org.opendaylight.controller + netconf-util + ${config.version} + + + org.opendaylight.controller + netconf-client + ${config.version} + + + org.opendaylight.controller + netconf-mapping-api + ${config.version} + + + org.opendaylight.controller + config-netconf-connector + ${config.version} + + + org.opendaylight.controller + config-persister-impl + ${config.version} + + + @@ -940,8 +989,35 @@ org.opendaylight.bgpcep util + + org.opendaylight.bgpcep + framework + - + + + io.netty + netty-handler + + + io.netty + netty-codec + + + io.netty + netty-buffer + + + io.netty + netty-transport + + + io.netty + netty-common + + + + org.opendaylight.controller clustering.test @@ -982,6 +1058,57 @@ yang-binding 0.6.0-SNAPSHOT + + org.opendaylight.yangtools + binding-type-provider + 0.6.0-SNAPSHOT + + + org.opendaylight.yangtools + binding-generator-util + ${yangtools.binding.version} + + + org.opendaylight.yangtools + binding-model-api + ${yangtools.binding.version} + + + org.opendaylight.yangtools + binding-generator-spi + ${yangtools.binding.version} + + + commons-lang + commons-lang + 2.4 + + + + org.opendaylight.yangtools.thirdparty + antlr-runtime-osgi + 4.0-SNAPSHOT + + + org.opendaylight.yangtools.thirdparty + xtend-lib-osgi + 2.4.2-SNAPSHOT + + + org.opendaylight.yangtools + yang-parser-api + ${yangtools.version} + + + org.opendaylight.yangtools + yang-model-util + ${yangtools.version} + + + org.opendaylight.yangtools + yang-parser-impl + ${yangtools.version} + org.opendaylight.yangtools yang-common diff --git a/opendaylight/distribution/opendaylight/src/main/resources/configuration/config.ini b/opendaylight/distribution/opendaylight/src/main/resources/configuration/config.ini index ac68652455..4ad7cf184b 100644 --- a/opendaylight/distribution/opendaylight/src/main/resources/configuration/config.ini +++ b/opendaylight/distribution/opendaylight/src/main/resources/configuration/config.ini @@ -13,6 +13,17 @@ osgi.bundles=\ reference\:file\:../lib/jersey-json-1.17.jar@2:start,\ reference\:file\:../lib/jersey-server-1.17.jar@2:start +# Netconf startup configuration +netconf.tcp.address=127.0.0.1 +netconf.tcp.port=8383 + +#netconf.tls.address=127.0.0.1 +#netconf.tls.port=8384 +#netconf.tls.keystore= +#netconf.tls.keystore.password= + +netconf.config.persister.storageAdapterClass=org.opendaylight.controller.netconf.persist.impl.NoOpStorageAdapter + # Set Default start level for framework osgi.bundles.defaultStartLevel=4 # Extra packages to import from the boot class loader diff --git a/opendaylight/distribution/opendaylight/src/main/resources/configuration/logback.xml b/opendaylight/distribution/opendaylight/src/main/resources/configuration/logback.xml index 50a87fb568..7d04a12a14 100644 --- a/opendaylight/distribution/opendaylight/src/main/resources/configuration/logback.xml +++ b/opendaylight/distribution/opendaylight/src/main/resources/configuration/logback.xml @@ -42,6 +42,9 @@ + + + diff --git a/opendaylight/netconf/config-netconf-connector/pom.xml b/opendaylight/netconf/config-netconf-connector/pom.xml new file mode 100755 index 0000000000..0386378321 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/pom.xml @@ -0,0 +1,157 @@ + + 4.0.0 + + + netconf-subsystem + org.opendaylight.controller + 0.2.1-SNAPSHOT + + config-netconf-connector + ${project.artifactId} + bundle + + + + + ${project.groupId} + config-api + + + ${project.groupId} + netconf-api + ${project.version} + + + org.slf4j + slf4j-api + + + com.google.guava + guava + + + ${project.groupId} + + yang-jmx-generator + + + ${project.groupId} + + config-util + + + ${project.groupId} + netconf-util + ${project.version} + + + ${project.groupId} + + yang-store-api + + + ${project.groupId} + netconf-mapping-api + ${project.version} + + + org.osgi + org.osgi.core + + + + ${project.groupId} + netconf-util + ${project.version} + test + test-jar + + + + ${project.groupId} + + yang-test + test + + + ${project.groupId} + + config-manager + ${config.version} + test + test-jar + + + ${project.groupId} + + config-manager + test + + + ${project.groupId} + netconf-impl + ${project.version} + test + + + ${project.groupId} + + yang-store-impl + test + + + org.opendaylight.bgpcep + mockito-configuration + test + + + + + + + org.apache.felix + maven-bundle-plugin + + + org.opendaylight.controller.netconf.confignetconfconnector.osgi.Activator + + + org.opendaylight.controller.netconf.confignetconfconnector.mapping.*, + org.opendaylight.controller.netconf.confignetconfconnector.operations.*, + org.opendaylight.controller.netconf.confignetconfconnector.transactions, + org.opendaylight.controller.netconf.confignetconfconnector.util, + org.opendaylight.controller.netconf.confignetconfconnector.osgi, + org.opendaylight.controller.config.util, + + + com.google.common.base, + com.google.common.collect, + javax.annotation, + javax.management, + javax.management.openmbean, + org.opendaylight.controller.config.api, + org.opendaylight.controller.config.api.jmx, + org.opendaylight.controller.config.yang.store.api, + org.opendaylight.controller.config.yangjmxgenerator, + org.opendaylight.controller.config.yangjmxgenerator.attribute, + org.opendaylight.controller.netconf.api, + org.opendaylight.controller.netconf.mapping.api, + org.opendaylight.controller.netconf.util.mapping, + org.opendaylight.controller.netconf.util.xml, + org.opendaylight.yangtools.yang.common, + org.opendaylight.yangtools.yang.model.api, + org.osgi.framework, + org.osgi.util.tracker, + org.slf4j, + org.w3c.dom + + + + + + + + + + diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/AttributeIfcSwitchStatement.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/AttributeIfcSwitchStatement.java new file mode 100644 index 0000000000..5e3f542214 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/AttributeIfcSwitchStatement.java @@ -0,0 +1,37 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes; + +import org.opendaylight.controller.config.yangjmxgenerator.attribute.*; + +public abstract class AttributeIfcSwitchStatement { + + public T switchAttribute(AttributeIfc attributeIfc) { + + if (attributeIfc instanceof JavaAttribute) { + return caseJavaAttribute((JavaAttribute) attributeIfc); + } else if (attributeIfc instanceof DependencyAttribute) { + return caseDependencyAttribute((DependencyAttribute) attributeIfc); + } else if (attributeIfc instanceof ListAttribute) { + return caseListAttribute((ListAttribute) attributeIfc); + } else if (attributeIfc instanceof TOAttribute) { + return caseTOAttribute((TOAttribute) attributeIfc); + } + + throw new IllegalArgumentException("Unknown attribute type " + attributeIfc.getClass() + ", " + attributeIfc); + } + + protected abstract T caseJavaAttribute(JavaAttribute attributeIfc); + + protected abstract T caseDependencyAttribute(DependencyAttribute attributeIfc); + + protected abstract T caseTOAttribute(TOAttribute attributeIfc); + + protected abstract T caseListAttribute(ListAttribute attributeIfc); +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/fromxml/AbstractAttributeReadingStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/fromxml/AbstractAttributeReadingStrategy.java new file mode 100644 index 0000000000..2ba1b61b80 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/fromxml/AbstractAttributeReadingStrategy.java @@ -0,0 +1,38 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.fromxml; + +import java.util.List; + +import org.opendaylight.controller.config.yangjmxgenerator.attribute.AttributeIfc; +import org.opendaylight.controller.netconf.util.xml.XmlElement; + +public abstract class AbstractAttributeReadingStrategy implements AttributeReadingStrategy { + + private final A attributeIfc; + + public AbstractAttributeReadingStrategy(A attributeIfc) { + this.attributeIfc = attributeIfc; + } + + public A getAttributeIfc() { + return attributeIfc; + } + + @Override + public AttributeConfigElement readElement(List configNodes) { + if (configNodes.size() == 0) + return AttributeConfigElement.createNullValue(attributeIfc); + + return readElementHook(configNodes); + } + + abstract AttributeConfigElement readElementHook(List configNodes); + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/fromxml/ArrayAttributeReadingStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/fromxml/ArrayAttributeReadingStrategy.java new file mode 100644 index 0000000000..f07e74035c --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/fromxml/ArrayAttributeReadingStrategy.java @@ -0,0 +1,39 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.fromxml; + +import com.google.common.collect.Lists; +import org.opendaylight.controller.config.yangjmxgenerator.attribute.AttributeIfc; +import org.opendaylight.controller.netconf.util.xml.XmlElement; + +import java.util.List; + +public class ArrayAttributeReadingStrategy extends AbstractAttributeReadingStrategy { + + private final AttributeReadingStrategy innerStrategy; + + /** + * @param attributeIfc + * @param innerStrategy + */ + public ArrayAttributeReadingStrategy(AttributeIfc attributeIfc, AttributeReadingStrategy innerStrategy) { + super(attributeIfc); + this.innerStrategy = innerStrategy; + } + + @Override + AttributeConfigElement readElementHook(List configNodes) { + List innerList = Lists.newArrayList(); + for (int i = 0; i < configNodes.size(); i++) { + innerList.add(innerStrategy.readElement(Lists.newArrayList(configNodes.get(i))).getValue()); + } + return AttributeConfigElement.create(getAttributeIfc(), innerList); + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/fromxml/AttributeConfigElement.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/fromxml/AttributeConfigElement.java new file mode 100644 index 0000000000..fa249da7f2 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/fromxml/AttributeConfigElement.java @@ -0,0 +1,76 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.fromxml; + +import javax.management.openmbean.OpenType; + +import org.opendaylight.controller.config.yangjmxgenerator.attribute.AttributeIfc; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.resolving.AttributeResolvingStrategy; + +import com.google.common.base.Optional; + +/** + * Parsed xml element containing configuration for one attribute of an instance + * of some module. Contains default value extracted from yang file. + */ +public class AttributeConfigElement { + private final Object dafaultValue; + private final Object value; + + private Optional resolvedValue; + private Object resolvedDefaultValue; + private String jmxName; + + public AttributeConfigElement(Object dafaultValue, Object value) { + this.dafaultValue = dafaultValue; + this.value = value; + } + + public void setJmxName(String jmxName) { + this.jmxName = jmxName; + } + + public String getJmxName() { + return jmxName; + } + + public void resolveValue(AttributeResolvingStrategy> attributeResolvingStrategy, + String attrName) { + resolvedValue = attributeResolvingStrategy.parseAttribute(attrName, value); + Optional resolvedDefault = attributeResolvingStrategy.parseAttribute(attrName, dafaultValue); + resolvedDefaultValue = resolvedDefault.isPresent() ? resolvedDefault.get() : null; + + } + + public static AttributeConfigElement create(AttributeIfc attributeIfc, Object value) { + return new AttributeConfigElement(attributeIfc.getNullableDefault(), value); + } + + public static AttributeConfigElement createNullValue(AttributeIfc attributeIfc) { + return new AttributeConfigElement(attributeIfc.getNullableDefault(), null); + } + + public Object getValue() { + return value; + } + + public Optional getResolvedValue() { + return resolvedValue; + } + + public Object getResolvedDefaultValue() { + return resolvedDefaultValue; + } + + @Override + public String toString() { + return "AttributeConfigElement [dafaultValue=" + dafaultValue + ", value=" + value + "]"; + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/fromxml/AttributeReadingStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/fromxml/AttributeReadingStrategy.java new file mode 100644 index 0000000000..6dae839148 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/fromxml/AttributeReadingStrategy.java @@ -0,0 +1,19 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.fromxml; + +import java.util.List; + +import org.opendaylight.controller.netconf.util.xml.XmlElement; + +public interface AttributeReadingStrategy { + + public abstract AttributeConfigElement readElement(List element); + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/fromxml/CompositeAttributeReadingStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/fromxml/CompositeAttributeReadingStrategy.java new file mode 100644 index 0000000000..1cfb74d652 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/fromxml/CompositeAttributeReadingStrategy.java @@ -0,0 +1,58 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.fromxml; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.opendaylight.controller.config.yangjmxgenerator.attribute.AttributeIfc; +import org.opendaylight.controller.config.yangjmxgenerator.attribute.TOAttribute; +import org.opendaylight.controller.netconf.util.xml.XmlElement; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +public class CompositeAttributeReadingStrategy extends AbstractAttributeReadingStrategy { + + private final Map innerStrategies; + + public CompositeAttributeReadingStrategy(TOAttribute attributeIfc, + Map innerStrategies) { + super(attributeIfc); + this.innerStrategies = innerStrategies; + } + + @Override + AttributeConfigElement readElementHook(List configNodes) { + + Preconditions.checkState(configNodes.size() == 1, "This element should be present only once %s", configNodes); + + XmlElement complexElement = configNodes.get(0); + + Map innerMap = Maps.newHashMap(); + + Map inner = getAttributeIfc().getYangPropertiesToTypesMap(); + + List recognisedChildren = Lists.newArrayList(); + for (Entry innerAttrEntry : inner.entrySet()) { + List childItem = complexElement.getChildElementsWithSameNamespace(innerAttrEntry.getKey()); + recognisedChildren.addAll(childItem); + + AttributeConfigElement resolvedInner = innerStrategies.get(innerAttrEntry.getKey()).readElement(childItem); + + innerMap.put(innerAttrEntry.getKey(), resolvedInner.getValue()); + } + + complexElement.checkUnrecognisedElements(recognisedChildren); + + return AttributeConfigElement.create(getAttributeIfc(), innerMap); + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/fromxml/ObjectNameAttributeReadingStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/fromxml/ObjectNameAttributeReadingStrategy.java new file mode 100644 index 0000000000..bdb9391e2b --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/fromxml/ObjectNameAttributeReadingStrategy.java @@ -0,0 +1,45 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.fromxml; + +import com.google.common.base.Preconditions; +import org.opendaylight.controller.config.yangjmxgenerator.attribute.AttributeIfc; +import org.opendaylight.controller.config.yangjmxgenerator.attribute.DependencyAttribute; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.mapping.ObjectNameAttributeMappingStrategy; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; + +import java.util.List; + +public class ObjectNameAttributeReadingStrategy extends AbstractAttributeReadingStrategy { + + public ObjectNameAttributeReadingStrategy(DependencyAttribute attributeIfc) { + super(attributeIfc); + } + + @Override + AttributeConfigElement readElementHook(List configNodes) { + + XmlElement firstChild = configNodes.get(0); + Preconditions.checkState(configNodes.size() == 1, "This element should be present only once " + firstChild + + " but was " + configNodes.size()); + + Preconditions.checkNotNull(firstChild, "Element %s should be present", firstChild); + return AttributeConfigElement.create(getAttributeIfc(), resolve(firstChild)); + } + + private ObjectNameAttributeMappingStrategy.MappedDependency resolve(XmlElement firstChild) { + XmlElement typeElement = firstChild.getOnlyChildElementWithSameNamespace(XmlNetconfConstants.TYPE_KEY); + String serviceName = typeElement.getTextContent(); + XmlElement nameElement = firstChild.getOnlyChildElementWithSameNamespace(XmlNetconfConstants.NAME_KEY); + String dependencyName = nameElement.getTextContent(); + + return new ObjectNameAttributeMappingStrategy.MappedDependency(serviceName, dependencyName); + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/fromxml/ObjectXmlReader.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/fromxml/ObjectXmlReader.java new file mode 100644 index 0000000000..8663de7365 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/fromxml/ObjectXmlReader.java @@ -0,0 +1,78 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.fromxml; + +import com.google.common.collect.Maps; +import org.opendaylight.controller.config.yangjmxgenerator.attribute.*; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.AttributeIfcSwitchStatement; + +import javax.management.openmbean.ArrayType; +import javax.management.openmbean.SimpleType; +import java.util.Map; +import java.util.Map.Entry; + +public class ObjectXmlReader extends AttributeIfcSwitchStatement { + + private String key; + + public Map prepareReading(Map yangToAttrConfig) { + Map strategies = Maps.newHashMap(); + + for (Entry attributeEntry : yangToAttrConfig.entrySet()) { + AttributeReadingStrategy strat = prepareReadingStrategy(attributeEntry.getKey(), attributeEntry.getValue()); + strategies.put(attributeEntry.getKey(), strat); + } + return strategies; + } + + private AttributeReadingStrategy prepareReadingStrategy(String key, AttributeIfc attributeIfc) { + this.key = key; + return switchAttribute(attributeIfc); + } + + @Override + protected AttributeReadingStrategy caseJavaAttribute(JavaAttribute attributeIfc) { + if (attributeIfc.getOpenType() instanceof SimpleType) + return new SimpleAttributeReadingStrategy(attributeIfc); + else if (attributeIfc.getOpenType() instanceof ArrayType) { + SimpleAttributeReadingStrategy innerStrategy = new SimpleAttributeReadingStrategy( + ((ArrayType) attributeIfc.getOpenType()).getElementOpenType()); + return new ArrayAttributeReadingStrategy(attributeIfc, innerStrategy); + } + throw new IllegalStateException(JavaAttribute.class + " can only provide open type " + SimpleType.class + + " or " + ArrayType.class); + } + + @Override + protected AttributeReadingStrategy caseDependencyAttribute(DependencyAttribute attributeIfc) { + return new ObjectNameAttributeReadingStrategy(attributeIfc); + } + + @Override + protected AttributeReadingStrategy caseTOAttribute(TOAttribute attributeIfc) { + Map inner = attributeIfc.getYangPropertiesToTypesMap(); + Map innerStrategies = Maps.newHashMap(); + + for (Entry innerAttrEntry : inner.entrySet()) { + AttributeReadingStrategy innerStrat = prepareReadingStrategy(innerAttrEntry.getKey(), + innerAttrEntry.getValue()); + innerStrategies.put(innerAttrEntry.getKey(), innerStrat); + } + + return new CompositeAttributeReadingStrategy(attributeIfc, innerStrategies); + } + + @Override + protected AttributeReadingStrategy caseListAttribute(ListAttribute attributeIfc) { + AttributeIfc innerAttr = attributeIfc.getInnerAttribute(); + AttributeReadingStrategy innerStrategy = prepareReadingStrategy(key, innerAttr); + return new ArrayAttributeReadingStrategy(attributeIfc, innerStrategy); + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/fromxml/SimpleAttributeReadingStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/fromxml/SimpleAttributeReadingStrategy.java new file mode 100644 index 0000000000..c5c287ffe2 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/fromxml/SimpleAttributeReadingStrategy.java @@ -0,0 +1,86 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.fromxml; + +import com.google.common.base.Preconditions; +import org.opendaylight.controller.config.yangjmxgenerator.attribute.AttributeIfc; +import org.opendaylight.controller.netconf.util.xml.XmlElement; + +import javax.management.openmbean.OpenType; +import java.util.List; + +public class SimpleAttributeReadingStrategy extends AbstractAttributeReadingStrategy { + + public SimpleAttributeReadingStrategy(AttributeIfc attributeIfc) { + super(attributeIfc); + } + + /** + * @param elementOpenType + */ + public SimpleAttributeReadingStrategy(OpenType elementOpenType) { + super(new AttributeIfcWrapper(elementOpenType)); + } + + @Override + AttributeConfigElement readElementHook(List configNodes) { + XmlElement xmlElement = configNodes.get(0); + Preconditions.checkState(configNodes.size() == 1, "This element should be present only once " + xmlElement + + " but was " + configNodes.size()); + + String textContent = xmlElement.getTextContent(); + + Preconditions.checkNotNull(textContent, "This element should contain text %s", xmlElement); + return AttributeConfigElement.create(getAttributeIfc(), textContent); + } + + /** + * Wrapper for JavaAttribute inner element attributes (in case JavaAttribute + * is array) + */ + static class AttributeIfcWrapper implements AttributeIfc { + + private final OpenType elementOpenType; + + public AttributeIfcWrapper(OpenType elementOpenType) { + this.elementOpenType = elementOpenType; + } + + @Override + public String getAttributeYangName() { + return null; + } + + @Override + public String getNullableDescription() { + return null; + } + + @Override + public String getNullableDefault() { + return null; + } + + @Override + public String getUpperCaseCammelCase() { + return null; + } + + @Override + public String getLowerCaseCammelCase() { + return null; + } + + @Override + public OpenType getOpenType() { + return elementOpenType; + } + + } +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/mapping/AbstractAttributeMappingStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/mapping/AbstractAttributeMappingStrategy.java new file mode 100644 index 0000000000..0d5fcb99b8 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/mapping/AbstractAttributeMappingStrategy.java @@ -0,0 +1,27 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.mapping; + +import javax.management.openmbean.OpenType; + +public abstract class AbstractAttributeMappingStrategy> implements + AttributeMappingStrategy { + + private final O attrOpenType; + + public AbstractAttributeMappingStrategy(O attributeIfc) { + this.attrOpenType = attributeIfc; + } + + @Override + public O getOpenType() { + return attrOpenType; + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/mapping/ArrayAttributeMappingStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/mapping/ArrayAttributeMappingStrategy.java new file mode 100644 index 0000000000..30436bb42f --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/mapping/ArrayAttributeMappingStrategy.java @@ -0,0 +1,60 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.mapping; + +import java.lang.reflect.Array; +import java.util.List; + +import javax.management.openmbean.ArrayType; +import javax.management.openmbean.OpenType; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; + +public class ArrayAttributeMappingStrategy extends AbstractAttributeMappingStrategy, ArrayType> { + + private final AttributeMappingStrategy> innerElementStrategy; + + public ArrayAttributeMappingStrategy(ArrayType arrayType, + AttributeMappingStrategy> innerElementStrategy) { + super(arrayType); + this.innerElementStrategy = innerElementStrategy; + } + + @Override + public Optional> mapAttribute(Object value) { + if (value == null) + return Optional.absent(); + + Preconditions.checkArgument(value.getClass().isArray(), "Value has to be instanceof Array "); + + List retVal = Lists.newArrayList(); + + for (int i = 0; i < Array.getLength(value); i++) { + Object innerValue = Array.get(value, i); + // String expectedClassName = + // getOpenType().getElementOpenType().getClassName(); + // String realClassName = value.getClass().getName(); + + // Preconditions.checkState(realClassName.contains(expectedClassName), + // "Element in collection/array should be of type " + + // expectedClassName + " but was " + // + realClassName + " for attribute: " + getOpenType()); + + Optional mapAttribute = innerElementStrategy.mapAttribute(innerValue); + + if (mapAttribute.isPresent()) + retVal.add(mapAttribute.get()); + } + + return Optional.of(retVal); + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/mapping/AttributeMappingStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/mapping/AttributeMappingStrategy.java new file mode 100644 index 0000000000..51dfc5873f --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/mapping/AttributeMappingStrategy.java @@ -0,0 +1,21 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.mapping; + +import com.google.common.base.Optional; + +import javax.management.openmbean.OpenType; + +public interface AttributeMappingStrategy> { + + O getOpenType(); + + Optional mapAttribute(Object o); + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/mapping/CompositeAttributeMappingStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/mapping/CompositeAttributeMappingStrategy.java new file mode 100644 index 0000000000..252b13bf68 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/mapping/CompositeAttributeMappingStrategy.java @@ -0,0 +1,69 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.mapping; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import org.opendaylight.controller.netconf.confignetconfconnector.util.Util; + +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenType; +import java.util.Map; +import java.util.Set; + +public class CompositeAttributeMappingStrategy extends + AbstractAttributeMappingStrategy, CompositeType> { + + private final Map>> innerStrategies; + private final Map jmxToJavaNameMapping; + + public CompositeAttributeMappingStrategy(CompositeType compositeType, + Map>> innerStrategies, + Map jmxToJavaNameMapping) { + super(compositeType); + this.innerStrategies = innerStrategies; + this.jmxToJavaNameMapping = jmxToJavaNameMapping; + } + + @Override + public Optional> mapAttribute(Object value) { + if (value == null) + return Optional.absent(); + + Util.checkType(value, CompositeDataSupport.class); + + CompositeDataSupport compositeData = (CompositeDataSupport) value; + CompositeType currentType = compositeData.getCompositeType(); + CompositeType expectedType = getOpenType(); + + Set expectedCompositeTypeKeys = expectedType.keySet(); + Set currentCompositeTypeKeys = currentType.keySet(); + Preconditions.checkArgument(expectedCompositeTypeKeys.equals(currentCompositeTypeKeys), + "Composite type mismatch, expected composite type with attributes " + expectedCompositeTypeKeys + + " but was " + currentCompositeTypeKeys); + + Map retVal = Maps.newHashMap(); + + for (String jmxName : jmxToJavaNameMapping.keySet()) { + String innerAttrJmxName = jmxName; + Object innerValue = compositeData.get(innerAttrJmxName); + + AttributeMappingStrategy> attributeMappingStrategy = innerStrategies + .get(innerAttrJmxName); + Optional mapAttribute = attributeMappingStrategy.mapAttribute(innerValue); + if (mapAttribute.isPresent()) + retVal.put(jmxToJavaNameMapping.get(innerAttrJmxName), mapAttribute.get()); + } + + return Optional.of(retVal); + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/mapping/ObjectMapper.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/mapping/ObjectMapper.java new file mode 100644 index 0000000000..52b6801cc0 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/mapping/ObjectMapper.java @@ -0,0 +1,97 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.mapping; + +import java.util.Map; +import java.util.Map.Entry; + +import javax.management.openmbean.ArrayType; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.SimpleType; + +import org.opendaylight.controller.config.yangjmxgenerator.attribute.*; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.AttributeIfcSwitchStatement; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config.Services; + +import com.google.common.collect.Maps; + +public class ObjectMapper extends AttributeIfcSwitchStatement>> { + + private final Services dependencyTracker; + + public ObjectMapper(Services depTracker) { + this.dependencyTracker = depTracker; + } + + public Map>> prepareMapping( + Map configDefinition) { + Map>> strategies = Maps.newHashMap(); + + for (Entry attrEntry : configDefinition.entrySet()) { + strategies.put(attrEntry.getKey(), prepareStrategy(attrEntry.getValue())); + } + + return strategies; + } + + private AttributeMappingStrategy> prepareStrategy(AttributeIfc attributeIfc) { + return switchAttribute(attributeIfc); + } + + private Map createJmxToYangMapping(TOAttribute attributeIfc) { + Map retVal = Maps.newHashMap(); + for (Entry entry : attributeIfc.getJmxPropertiesToTypesMap().entrySet()) { + retVal.put(entry.getKey(), (entry.getValue()).getAttributeYangName()); + } + return retVal; + } + + @Override + protected AttributeMappingStrategy> caseJavaAttribute(JavaAttribute attributeIfc) { + + if (attributeIfc.getOpenType() instanceof SimpleType) + return new SimpleAttributeMappingStrategy((SimpleType) attributeIfc.getOpenType()); + else if (attributeIfc.getOpenType() instanceof ArrayType) { + ArrayType arrayType = (ArrayType) attributeIfc.getOpenType(); + AttributeMappingStrategy> innerStrategy = new SimpleAttributeMappingStrategy( + (SimpleType) arrayType.getElementOpenType()); + return new ArrayAttributeMappingStrategy(arrayType, innerStrategy); + } + throw new IllegalStateException(JavaAttribute.class + " can only provide open type " + SimpleType.class + + " or " + ArrayType.class); + } + + @Override + protected AttributeMappingStrategy> caseDependencyAttribute( + DependencyAttribute attributeIfc) { + String serviceName = attributeIfc.getDependency().getSie().getQName().getLocalName(); + return new ObjectNameAttributeMappingStrategy((SimpleType) attributeIfc.getOpenType(), dependencyTracker, + serviceName); + } + + @Override + protected AttributeMappingStrategy> caseTOAttribute(TOAttribute attributeIfc) { + Map>> innerStrategies = Maps.newHashMap(); + + for (Entry innerAttrEntry : attributeIfc.getJmxPropertiesToTypesMap().entrySet()) { + innerStrategies.put(innerAttrEntry.getKey(), prepareStrategy(innerAttrEntry.getValue())); + } + + return new CompositeAttributeMappingStrategy((CompositeType) attributeIfc.getOpenType(), innerStrategies, + createJmxToYangMapping(attributeIfc)); + } + + @Override + protected AttributeMappingStrategy> caseListAttribute(ListAttribute attributeIfc) { + return new ArrayAttributeMappingStrategy(attributeIfc.getOpenType(), + prepareStrategy(attributeIfc.getInnerAttribute())); + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/mapping/ObjectNameAttributeMappingStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/mapping/ObjectNameAttributeMappingStrategy.java new file mode 100644 index 0000000000..1febf02a2d --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/mapping/ObjectNameAttributeMappingStrategy.java @@ -0,0 +1,74 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.mapping; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config.Services; +import org.opendaylight.controller.netconf.confignetconfconnector.util.Util; + +import javax.management.ObjectName; +import javax.management.openmbean.SimpleType; + +public class ObjectNameAttributeMappingStrategy extends + AbstractAttributeMappingStrategy> { + + private final Services tracker; + private final String serviceName; + + public ObjectNameAttributeMappingStrategy(SimpleType openType, Services dependencyTracker, String serviceName) { + super(openType); + this.tracker = dependencyTracker; + this.serviceName = serviceName; + } + + @Override + public Optional mapAttribute(Object value) { + if (value == null) + return Optional.absent(); + + String expectedClass = getOpenType().getClassName(); + String realClass = value.getClass().getName(); + Preconditions.checkArgument(realClass.equals(expectedClass), "Type mismatch, expected " + expectedClass + + " but was " + realClass); + Util.checkType(value, ObjectName.class); + + ObjectName on = (ObjectName) value; + String refName = tracker.addServiceEntry(serviceName, on); + + return Optional.of(new MappedDependency(serviceName, refName)); + } + + public static class MappedDependency { + private final String serviceName, refName; + + public MappedDependency(String serviceName, String refName) { + this.serviceName = serviceName; + this.refName = refName; + } + + public String getServiceName() { + return serviceName; + } + + public String getRefName() { + return refName; + } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("MappedDependency{"); + sb.append("serviceName='").append(serviceName).append('\''); + sb.append(", refName='").append(refName).append('\''); + sb.append('}'); + return sb.toString(); + } + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/mapping/SimpleAttributeMappingStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/mapping/SimpleAttributeMappingStrategy.java new file mode 100644 index 0000000000..d92b7c432d --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/mapping/SimpleAttributeMappingStrategy.java @@ -0,0 +1,72 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.mapping; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import org.opendaylight.controller.netconf.confignetconfconnector.util.Util; + +import javax.management.openmbean.SimpleType; +import java.util.Date; +import java.util.Map; + +public class SimpleAttributeMappingStrategy extends AbstractAttributeMappingStrategy> { + + public SimpleAttributeMappingStrategy(SimpleType openType) { + super(openType); + } + + @Override + public Optional mapAttribute(Object value) { + if (value == null) + return Optional.absent(); + + String expectedClass = getOpenType().getClassName(); + String realClass = value.getClass().getName(); + Preconditions.checkArgument(realClass.equals(expectedClass), "Type mismatch, expected " + expectedClass + + " but was " + realClass); + + WriterPlugin prefferedPlugin = writerPlugins.get(value.getClass().getCanonicalName()); + prefferedPlugin = prefferedPlugin == null ? writerPlugins.get(DEFAULT_WRITER_PLUGIN) : prefferedPlugin; + return Optional.of(prefferedPlugin.writeObject(value)); + } + + private static final String DEFAULT_WRITER_PLUGIN = "default"; + private static final Map writerPlugins = Maps.newHashMap(); + static { + writerPlugins.put(DEFAULT_WRITER_PLUGIN, new DefaultWriterPlugin()); + writerPlugins.put(Date.class.getCanonicalName(), new DatePlugin()); + } + + /** + * Custom writer plugins must implement this interface. + */ + static interface WriterPlugin { + String writeObject(Object value); + } + + static class DefaultWriterPlugin implements WriterPlugin { + + @Override + public String writeObject(Object value) { + return value.toString(); + } + } + + static class DatePlugin implements WriterPlugin { + + @Override + public String writeObject(Object value) { + Preconditions.checkArgument(value instanceof Date, "Attribute must be Date"); + return Util.writeDate((Date) value); + } + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/resolving/AbstractAttributeResolvingStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/resolving/AbstractAttributeResolvingStrategy.java new file mode 100644 index 0000000000..2808d5dfff --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/resolving/AbstractAttributeResolvingStrategy.java @@ -0,0 +1,24 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.resolving; + +import javax.management.openmbean.OpenType; + +abstract class AbstractAttributeResolvingStrategy> implements AttributeResolvingStrategy { + private final O openType; + + public AbstractAttributeResolvingStrategy(O openType) { + this.openType = openType; + } + + @Override + public O getOpenType() { + return openType; + } +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/resolving/ArrayAttributeResolvingStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/resolving/ArrayAttributeResolvingStrategy.java new file mode 100644 index 0000000000..adee8bec58 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/resolving/ArrayAttributeResolvingStrategy.java @@ -0,0 +1,105 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.resolving; + +import com.google.common.base.Optional; +import org.opendaylight.controller.netconf.confignetconfconnector.util.Util; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.management.openmbean.ArrayType; +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenType; +import java.lang.reflect.Array; +import java.util.List; + +final class ArrayAttributeResolvingStrategy extends AbstractAttributeResolvingStrategy> { + + private final AttributeResolvingStrategy> innerTypeResolvingStrategy; + + private static final Logger logger = LoggerFactory.getLogger(ArrayAttributeResolvingStrategy.class); + + public ArrayAttributeResolvingStrategy(AttributeResolvingStrategy> innerTypeResolved, + ArrayType openType) { + super(openType); + this.innerTypeResolvingStrategy = innerTypeResolved; + } + + @Override + public Optional parseAttribute(String attrName, Object value) { + if (value == null) { + return Optional.absent(); + } + + Util.checkType(value, List.class); + List valueList = (List) value; + + Class innerTypeClass = null; + + if (innerTypeResolvingStrategy.getOpenType() instanceof CompositeType) { + innerTypeClass = CompositeDataSupport.class; + } else + try { + innerTypeClass = Class.forName(getOpenType().getElementOpenType().getClassName()); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Unable to locate class for " + + getOpenType().getElementOpenType().getClassName(), e); + } + + Object parsedArray = null; + + if (getOpenType().isPrimitiveArray()) { + Class primitiveType = getPrimitiveType(innerTypeClass); + parsedArray = Array.newInstance(primitiveType, valueList.size()); + } else + parsedArray = Array.newInstance(innerTypeClass, valueList.size()); + + int i = 0; + for (Object innerValue : valueList) { + Optional parsedElement = innerTypeResolvingStrategy.parseAttribute(attrName + "_" + i, innerValue); + + if (!parsedElement.isPresent()) + continue; + + Array.set(parsedArray, i, parsedElement.get()); + // parsedArray[i] = parsedElement.get(); + i++; + } + + logger.debug("Attribute {} : {} parsed to type {} as {}", attrName, value, getOpenType(), + toStringArray(parsedArray)); + + return Optional.of(parsedArray); + } + + private static String toStringArray(Object array) { + StringBuilder build = new StringBuilder(array.toString()); + build.append(" ["); + for (int i = 0; i < Array.getLength(array); i++) { + build.append(Array.get(array, i).toString()); + build.append(","); + } + build.append("]]"); + return build.toString(); + } + + private static Class getPrimitiveType(Class innerTypeClass) { + try { + return (Class) innerTypeClass.getField("TYPE").get(null); + } catch (Exception e) { + throw new RuntimeException("Unable to determine primitive type to " + innerTypeClass); + } + } + + @Override + public String toString() { + return "ResolvedArrayTypeAttributeType [innerTypeResolved=" + innerTypeResolvingStrategy + "]"; + } +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/resolving/AttributeResolvingStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/resolving/AttributeResolvingStrategy.java new file mode 100644 index 0000000000..0bb274ae41 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/resolving/AttributeResolvingStrategy.java @@ -0,0 +1,22 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.resolving; + +import com.google.common.base.Optional; + +import javax.management.openmbean.OpenType; + +/** + * Create real object from String or Map that corresponds to given opentype. + */ +public interface AttributeResolvingStrategy> { + O getOpenType(); + + Optional parseAttribute(String attrName, Object value); +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/resolving/CompositeAttributeResolvingStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/resolving/CompositeAttributeResolvingStrategy.java new file mode 100644 index 0000000000..a85f3064cf --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/resolving/CompositeAttributeResolvingStrategy.java @@ -0,0 +1,85 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.resolving; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import org.opendaylight.controller.netconf.confignetconfconnector.util.Util; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.OpenType; +import java.util.Map; + +final class CompositeAttributeResolvingStrategy extends + AbstractAttributeResolvingStrategy { + private final Map>> innerTypes; + private final Map yangToJavaAttrMapping; + + private static final Logger logger = LoggerFactory.getLogger(CompositeAttributeResolvingStrategy.class); + + CompositeAttributeResolvingStrategy(Map>> innerTypes, + CompositeType openType, Map yangToJavaAttrMapping) { + super(openType); + this.innerTypes = innerTypes; + this.yangToJavaAttrMapping = yangToJavaAttrMapping; + } + + @Override + public String toString() { + return "ResolvedCompositeAttribute [" + innerTypes + "]"; + } + + @Override + public Optional parseAttribute(String attrName, Object value) { + + if (value == null) { + return Optional.absent(); + } + + Util.checkType(value, Map.class); + Map valueMap = (Map) value; + + Map items = Maps.newHashMap(); + Map> openTypes = Maps.newHashMap(); + + for (Object innerAttrName : innerTypes.keySet()) { + Preconditions.checkState(innerAttrName instanceof String, "Attribute name must be string"); + String innerAttrNameStr = (String) innerAttrName; + + AttributeResolvingStrategy> attributeResolvingStrategy = innerTypes + .get(innerAttrName); + + Object valueToParse = valueMap.get(innerAttrName); + + Optional parsedInnerValue = attributeResolvingStrategy.parseAttribute(innerAttrNameStr, valueToParse); + + openTypes.put(innerAttrNameStr, attributeResolvingStrategy.getOpenType()); + + items.put(yangToJavaAttrMapping.get(innerAttrNameStr), + parsedInnerValue.isPresent() ? parsedInnerValue.get() : null); + } + + CompositeDataSupport parsedValue = null; + try { + parsedValue = new CompositeDataSupport(getOpenType(), items); + } catch (OpenDataException e) { + throw new RuntimeException("An error occured during restoration of composite type " + this + + " for attribute " + attrName + " from value " + value, e); + } + + logger.debug("Attribute {} : {} parsed to type {} as {}", attrName, value, getOpenType(), parsedValue); + + return Optional.of(parsedValue); + } +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/resolving/ObjectNameAttributeResolvingStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/resolving/ObjectNameAttributeResolvingStrategy.java new file mode 100644 index 0000000000..5469015a23 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/resolving/ObjectNameAttributeResolvingStrategy.java @@ -0,0 +1,49 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.resolving; + +import com.google.common.base.Optional; +import org.opendaylight.controller.config.api.jmx.ObjectNameUtil; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.mapping.ObjectNameAttributeMappingStrategy; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config.Services; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config.Services.ServiceInstance; +import org.opendaylight.controller.netconf.confignetconfconnector.util.Util; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.management.ObjectName; +import javax.management.openmbean.SimpleType; + +public class ObjectNameAttributeResolvingStrategy extends AbstractAttributeResolvingStrategy> { + + private final Services serviceTracker; + private static final Logger logger = LoggerFactory.getLogger(ObjectNameAttributeResolvingStrategy.class); + + ObjectNameAttributeResolvingStrategy(Services serviceTracker) { + super(SimpleType.OBJECTNAME); + this.serviceTracker = serviceTracker; + } + + @Override + public Optional parseAttribute(String attrName, Object value) { + if (value == null) { + return Optional.absent(); + } + + Util.checkType(value, ObjectNameAttributeMappingStrategy.MappedDependency.class); + + ObjectNameAttributeMappingStrategy.MappedDependency mappedDep = (ObjectNameAttributeMappingStrategy.MappedDependency) value; + ServiceInstance byRefName = serviceTracker.getByServiceAndRefName(mappedDep.getServiceName(), + mappedDep.getRefName()); + ObjectName on = ObjectNameUtil.createReadOnlyModuleON(byRefName.getModuleName(), byRefName.getInstanceName()); + logger.debug("Attribute {} : {} parsed to type {}", attrName, value, getOpenType()); + return Optional.of(on); + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/resolving/ObjectResolver.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/resolving/ObjectResolver.java new file mode 100644 index 0000000000..386047165e --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/resolving/ObjectResolver.java @@ -0,0 +1,104 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.resolving; + +import java.util.Map; +import java.util.Map.Entry; + +import javax.management.openmbean.ArrayType; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.SimpleType; + +import org.opendaylight.controller.config.yangjmxgenerator.attribute.*; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.AttributeIfcSwitchStatement; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config.Services; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; + +public class ObjectResolver extends AttributeIfcSwitchStatement>> { + + private final Services serviceTracker; + private OpenType openType; + + public ObjectResolver(Services serviceTracker) { + this.serviceTracker = serviceTracker; + } + + public Map>> prepareResolving( + Map configDefinition) { + Map>> strategies = Maps.newHashMap(); + + for (Entry attrEntry : configDefinition.entrySet()) { + strategies.put(attrEntry.getKey(), + prepareStrategy(attrEntry.getValue(), attrEntry.getValue().getOpenType())); + } + + return strategies; + } + + private AttributeResolvingStrategy> prepareStrategy(AttributeIfc attributeIfc, + OpenType openType) { + + this.openType = openType; + return switchAttribute(attributeIfc); + } + + private Map createYangToJmxMapping(TOAttribute attributeIfc) { + Map retVal = Maps.newHashMap(); + for (Entry entry : attributeIfc.getYangPropertiesToTypesMap().entrySet()) { + retVal.put(entry.getKey(), (entry.getValue()).getLowerCaseCammelCase()); + } + return retVal; + } + + @Override + protected AttributeResolvingStrategy> caseJavaAttribute(JavaAttribute attributeIfc) { + if (attributeIfc.getOpenType() instanceof SimpleType) + return new SimpleAttributeResolvingStrategy((SimpleType) openType); + else if (attributeIfc.getOpenType() instanceof ArrayType) { + ArrayType arrayType = (ArrayType) openType; + SimpleType innerType = (SimpleType) arrayType.getElementOpenType(); + AttributeResolvingStrategy> strat = new SimpleAttributeResolvingStrategy(innerType); + return new ArrayAttributeResolvingStrategy(strat, arrayType); + } + throw new IllegalStateException(JavaAttribute.class + " can only provide open type " + SimpleType.class + + " or " + ArrayType.class); + } + + @Override + protected AttributeResolvingStrategy> caseDependencyAttribute( + DependencyAttribute attributeIfc) { + return new ObjectNameAttributeResolvingStrategy(serviceTracker); + } + + @Override + protected AttributeResolvingStrategy> caseTOAttribute(TOAttribute attributeIfc) { + CompositeType compositeType = (CompositeType) openType; + Map>> innerMap = Maps.newHashMap(); + for (String innerName : compositeType.keySet()) { + Preconditions.checkState(attributeIfc instanceof TOAttribute, "Unexpected state, " + attributeIfc + + " should be instance of " + TOAttribute.class.getName()); + AttributeIfc innerAttributeIfc = attributeIfc.getJmxPropertiesToTypesMap().get(innerName); + innerMap.put(innerAttributeIfc.getAttributeYangName(), + prepareStrategy(innerAttributeIfc, compositeType.getType(innerName))); + } + return new CompositeAttributeResolvingStrategy(innerMap, compositeType, createYangToJmxMapping(attributeIfc)); + } + + @Override + protected AttributeResolvingStrategy> caseListAttribute(ListAttribute attributeIfc) { + ArrayType arrayType = (ArrayType) openType; + OpenType innerType = arrayType.getElementOpenType(); + AttributeIfc inner = attributeIfc.getInnerAttribute(); + return new ArrayAttributeResolvingStrategy(prepareStrategy(inner, innerType), arrayType); + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/resolving/SimpleAttributeResolvingStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/resolving/SimpleAttributeResolvingStrategy.java new file mode 100644 index 0000000000..3b881579f1 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/resolving/SimpleAttributeResolvingStrategy.java @@ -0,0 +1,125 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.resolving; + +import com.google.common.base.Optional; +import com.google.common.collect.Maps; +import org.opendaylight.controller.netconf.confignetconfconnector.util.Util; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.management.openmbean.SimpleType; +import java.lang.reflect.Method; +import java.math.BigInteger; +import java.util.Date; +import java.util.Map; + +final class SimpleAttributeResolvingStrategy extends AbstractAttributeResolvingStrategy> { + + private static final Logger logger = LoggerFactory.getLogger(SimpleAttributeResolvingStrategy.class); + + SimpleAttributeResolvingStrategy(SimpleType simpleType) { + super(simpleType); + } + + @Override + public String toString() { + return "ResolvedSimpleAttribute [" + getOpenType().getClassName() + "]"; + } + + @Override + public Optional parseAttribute(String attrName, Object value) { + if (value == null) { + return Optional.absent(); + } + + Class cls; + try { + cls = Class.forName(getOpenType().getClassName()); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Unable to locate class for " + getOpenType().getClassName(), e); + } + + Util.checkType(value, String.class); + + Resolver prefferedPlugin = resolverPlugins.get(cls.getCanonicalName()); + prefferedPlugin = prefferedPlugin == null ? resolverPlugins.get(DEFAULT_RESOLVERS) : prefferedPlugin; + + Object parsedValue = prefferedPlugin.resolveObject(cls, attrName, (String) value); + logger.debug("Attribute {} : {} parsed to type {} with value {}", attrName, value, getOpenType(), parsedValue); + return Optional.of(parsedValue); + } + + private static final String DEFAULT_RESOLVERS = "default"; + private static final Map resolverPlugins = Maps.newHashMap(); + + static { + resolverPlugins.put(DEFAULT_RESOLVERS, new DefaultResolver()); + resolverPlugins.put(String.class.getCanonicalName(), new StringResolver()); + resolverPlugins.put(Date.class.getCanonicalName(), new DateResolver()); + resolverPlugins.put(Character.class.getCanonicalName(), new CharResolver()); + resolverPlugins.put(BigInteger.class.getCanonicalName(), new BigIntegerResolver()); + } + + static interface Resolver { + Object resolveObject(Class type, String attrName, String value); + } + + static class DefaultResolver implements Resolver { + + @Override + public Object resolveObject(Class type, String attrName, String value) { + try { + Object parsedValue = parseObject(type, value); + return parsedValue; + } catch (Exception e) { + throw new RuntimeException("Unable to resolve attribute " + attrName + " from " + value, e); + } + } + + protected Object parseObject(Class type, String value) throws Exception { + Method method = type.getMethod("valueOf", String.class); + Object parsedValue = method.invoke(null, value); + return parsedValue; + } + } + + static class StringResolver extends DefaultResolver { + + @Override + protected Object parseObject(Class type, String value) throws Exception { + return value; + } + } + + static class BigIntegerResolver extends DefaultResolver { + + @Override + protected Object parseObject(Class type, String value) throws Exception { + return new BigInteger(value); + } + } + + static class CharResolver extends DefaultResolver { + + @Override + protected Object parseObject(Class type, String value) throws Exception { + return new Character(value.charAt(0)); + } + } + + static class DateResolver extends DefaultResolver { + + @Override + protected Object parseObject(Class type, String value) throws Exception { + return Util.readDate(value); + } + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/toxml/ArrayAttributeWritingStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/toxml/ArrayAttributeWritingStrategy.java new file mode 100644 index 0000000000..89dc251b4e --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/toxml/ArrayAttributeWritingStrategy.java @@ -0,0 +1,34 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.toxml; + +import java.util.List; + +import org.opendaylight.controller.netconf.confignetconfconnector.util.Util; +import org.w3c.dom.Element; + +public class ArrayAttributeWritingStrategy implements AttributeWritingStrategy { + + private final AttributeWritingStrategy innnerStrategy; + + public ArrayAttributeWritingStrategy(AttributeWritingStrategy innerStrategy) { + this.innnerStrategy = innerStrategy; + } + + @Override + public void writeElement(Element parentElement, String namespace, Object value) { + Util.checkType(value, List.class); + + for (Object innerObject : ((List) value)) { + innnerStrategy.writeElement(parentElement, namespace, innerObject); + } + + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/toxml/AttributeWritingStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/toxml/AttributeWritingStrategy.java new file mode 100644 index 0000000000..d0c2b885f7 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/toxml/AttributeWritingStrategy.java @@ -0,0 +1,17 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.toxml; + +import org.w3c.dom.Element; + +public interface AttributeWritingStrategy { + + void writeElement(Element parentElement, String namespace, Object value); + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/toxml/CompositeAttributeWritingStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/toxml/CompositeAttributeWritingStrategy.java new file mode 100644 index 0000000000..d1326bde8e --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/toxml/CompositeAttributeWritingStrategy.java @@ -0,0 +1,51 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.toxml; + +import java.util.Map; +import java.util.Map.Entry; + +import org.opendaylight.controller.netconf.confignetconfconnector.util.Util; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class CompositeAttributeWritingStrategy implements AttributeWritingStrategy { + + protected final String key; + protected final Document document; + protected final Map innerStrats; + + public CompositeAttributeWritingStrategy(Document document, String key, + Map innerStrats) { + this.document = document; + this.key = key; + this.innerStrats = innerStrats; + } + + @Override + public void writeElement(Element parentElement, String namespace, Object value) { + Util.checkType(value, Map.class); + + Element innerNode = document.createElement(key); + XmlUtil.addNamespaceAttr(innerNode, namespace); + + Map map = (Map) value; + + for (Entry innerObjectEntry : map.entrySet()) { + Util.checkType(innerObjectEntry.getKey(), String.class); + + String innerKey = (String) innerObjectEntry.getKey(); + Object innerValue = innerObjectEntry.getValue(); + + innerStrats.get(innerKey).writeElement(innerNode, namespace, innerValue); + } + parentElement.appendChild(innerNode); + } +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/toxml/ObjectNameAttributeWritingStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/toxml/ObjectNameAttributeWritingStrategy.java new file mode 100644 index 0000000000..b88b6722c8 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/toxml/ObjectNameAttributeWritingStrategy.java @@ -0,0 +1,50 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.toxml; + +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.mapping.ObjectNameAttributeMappingStrategy; +import org.opendaylight.controller.netconf.confignetconfconnector.util.Util; +import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class ObjectNameAttributeWritingStrategy implements AttributeWritingStrategy { + + private final Document document; + private final String key; + + /** + * @param document + * @param key + */ + public ObjectNameAttributeWritingStrategy(Document document, String key) { + this.document = document; + this.key = key; + } + + @Override + public void writeElement(Element parentElement, String namespace, Object value) { + Util.checkType(value, ObjectNameAttributeMappingStrategy.MappedDependency.class); + Element innerNode = document.createElement(key); + XmlUtil.addNamespaceAttr(innerNode, namespace); + + String moduleName = ((ObjectNameAttributeMappingStrategy.MappedDependency) value).getServiceName(); + String refName = ((ObjectNameAttributeMappingStrategy.MappedDependency) value).getRefName(); + + final Element typeElement = XmlUtil.createTextElement(document, XmlNetconfConstants.TYPE_KEY, moduleName); + innerNode.appendChild(typeElement); + + final Element nameElement = XmlUtil.createTextElement(document, XmlNetconfConstants.NAME_KEY, refName); + innerNode.appendChild(nameElement); + + parentElement.appendChild(innerNode); + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/toxml/ObjectXmlWriter.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/toxml/ObjectXmlWriter.java new file mode 100644 index 0000000000..b49d20ed35 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/toxml/ObjectXmlWriter.java @@ -0,0 +1,89 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.toxml; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import org.opendaylight.controller.config.yangjmxgenerator.attribute.*; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.AttributeIfcSwitchStatement; +import org.w3c.dom.Document; + +import javax.management.openmbean.ArrayType; +import javax.management.openmbean.SimpleType; +import java.util.Map; +import java.util.Map.Entry; + +public class ObjectXmlWriter extends AttributeIfcSwitchStatement { + + private Document document; + private String key; + + public Map prepareWriting(Map yangToAttrConfig, + Document document) { + + Map preparedWriting = Maps.newHashMap(); + + for (Entry mappedAttributeEntry : yangToAttrConfig.entrySet()) { + String key = mappedAttributeEntry.getKey(); + AttributeIfc value = mappedAttributeEntry.getValue(); + AttributeWritingStrategy strat = prepareWritingStrategy(key, value, document); + preparedWriting.put(key, strat); + } + + return preparedWriting; + } + + private AttributeWritingStrategy prepareWritingStrategy(String key, AttributeIfc expectedAttr, Document document) { + Preconditions.checkNotNull(expectedAttr, "Mbean attributes mismatch, unable to find expected attribute for %s", + key); + this.document = document; + this.key = key; + return switchAttribute(expectedAttr); + } + + @Override + protected AttributeWritingStrategy caseJavaAttribute(JavaAttribute attributeIfc) { + + if (attributeIfc.getOpenType() instanceof SimpleType) + return new SimpleAttributeWritingStrategy(document, key); + else if (attributeIfc.getOpenType() instanceof ArrayType) { + AttributeWritingStrategy innerStrategy = new SimpleAttributeWritingStrategy(document, key); + return new ArrayAttributeWritingStrategy(innerStrategy); + } + throw new IllegalStateException(JavaAttribute.class + " can only provide open type " + SimpleType.class + + " or " + ArrayType.class); + } + + @Override + protected AttributeWritingStrategy caseDependencyAttribute(DependencyAttribute attributeIfc) { + return new ObjectNameAttributeWritingStrategy(document, key); + } + + @Override + protected AttributeWritingStrategy caseTOAttribute(TOAttribute attributeIfc) { + Map innerStrats = Maps.newHashMap(); + String currentKey = key; + for (Entry innerAttrEntry : attributeIfc.getYangPropertiesToTypesMap().entrySet()) { + + AttributeWritingStrategy innerStrategy = prepareWritingStrategy(innerAttrEntry.getKey(), + innerAttrEntry.getValue(), document); + innerStrats.put(innerAttrEntry.getKey(), innerStrategy); + } + + return new CompositeAttributeWritingStrategy(document, currentKey, innerStrats); + } + + @Override + protected AttributeWritingStrategy caseListAttribute(ListAttribute attributeIfc) { + AttributeIfc inner = attributeIfc.getInnerAttribute(); + AttributeWritingStrategy innerStrategy = prepareWritingStrategy(key, inner, document); + return new ArrayAttributeWritingStrategy(innerStrategy); + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/toxml/RuntimeBeanEntryWritingStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/toxml/RuntimeBeanEntryWritingStrategy.java new file mode 100644 index 0000000000..a67b348f9f --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/toxml/RuntimeBeanEntryWritingStrategy.java @@ -0,0 +1,64 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.toxml; + +import java.util.Map; +import java.util.Map.Entry; + +import org.opendaylight.controller.netconf.confignetconfconnector.util.Util; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class RuntimeBeanEntryWritingStrategy extends CompositeAttributeWritingStrategy { + + public RuntimeBeanEntryWritingStrategy(Document document, String key, + Map innerStrats) { + super(document, key, innerStrats); + } + + /* + * (non-Javadoc) + * + * @see org.opendaylight.controller.config.netconf.mapping.attributes.toxml. + * AttributeWritingStrategy#writeElement(org.w3c.dom.Element, + * java.lang.Object) + */ + @Override + public void writeElement(Element parentElement, String namespace, Object value) { + Util.checkType(value, Map.class); + + Element innerNode = document.createElement(key); + + Map map = (Map) value; + + for (Entry runtimeBeanInstanceMappingEntry : map.entrySet()) { + + // wrap runtime attributes with number assigned to current runtime + // bean + Util.checkType(runtimeBeanInstanceMappingEntry.getValue(), Map.class); + Map innerMap = (Map) runtimeBeanInstanceMappingEntry.getValue(); + Element runtimeInstanceNode = document.createElement("_" + + (String) runtimeBeanInstanceMappingEntry.getKey()); + innerNode.appendChild(runtimeInstanceNode); + + for (Entry innerObjectEntry : innerMap.entrySet()) { + + Util.checkType(innerObjectEntry.getKey(), String.class); + + String innerKey = (String) innerObjectEntry.getKey(); + Object innerValue = innerObjectEntry.getValue(); + + innerStrats.get(innerKey).writeElement(runtimeInstanceNode, namespace, innerValue); + } + } + parentElement.appendChild(innerNode); + + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/toxml/SimpleAttributeWritingStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/toxml/SimpleAttributeWritingStrategy.java new file mode 100644 index 0000000000..514183be2f --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/attributes/toxml/SimpleAttributeWritingStrategy.java @@ -0,0 +1,38 @@ +/* + * 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.netconf.confignetconfconnector.mapping.attributes.toxml; + +import org.opendaylight.controller.netconf.confignetconfconnector.util.Util; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class SimpleAttributeWritingStrategy implements AttributeWritingStrategy { + + private final Document document; + private final String key; + + /** + * @param document + * @param key + */ + public SimpleAttributeWritingStrategy(Document document, String key) { + this.document = document; + this.key = key; + } + + @Override + public void writeElement(Element parentElement, String namespace, Object value) { + Util.checkType(value, String.class); + Element innerNode = XmlUtil.createTextElement(document, key, (String) value); + XmlUtil.addNamespaceAttr(innerNode, namespace); + parentElement.appendChild(innerNode); + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/config/Config.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/config/Config.java new file mode 100644 index 0000000000..b17a8a8833 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/config/Config.java @@ -0,0 +1,248 @@ +/* + * 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.netconf.confignetconfconnector.mapping.config; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import org.opendaylight.controller.config.api.jmx.ObjectNameUtil; +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.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.management.ObjectName; +import java.util.*; +import java.util.Map.Entry; + +import static com.google.common.base.Preconditions.checkState; +import static java.lang.String.format; + +public class Config { + + private final Map> moduleConfigs; + + public Config(Map> moduleConfigs) { + this.moduleConfigs = moduleConfigs; + } + + private Map>> getMappedInstances(Set instancesToMap, + Services serviceTracker) { + Multimap moduleToInstances = mapInstancesToModules(instancesToMap); + + Map>> retVal = Maps.newLinkedHashMap(); + + for (String namespace : moduleConfigs.keySet()) { + + Map> innerRetVal = Maps.newHashMap(); + + for (Entry mbeEntry : moduleConfigs.get(namespace).entrySet()) { + + String moduleName = mbeEntry.getKey(); + Collection instances = moduleToInstances.get(moduleName); + + if (instances == null) + continue; + + innerRetVal.put(moduleName, instances); + + // All found instances add to service tracker in advance + // This way all instances will be serialized as all available + // services when get-config is triggered + // (even if they are not used as services by other onstances) + // = more user friendly + addServices(serviceTracker, instances, mbeEntry.getValue().getProvidedServices()); + + } + + retVal.put(namespace, innerRetVal); + } + return retVal; + } + + private void addServices(Services serviceTracker, Collection instances, + Collection providedServices) { + for (ObjectName instanceOn : instances) { + for (String serviceName : providedServices) { + serviceTracker.addServiceEntry(serviceName, instanceOn); + } + } + } + + private static Multimap mapInstancesToModules(Set instancesToMap) { + Multimap retVal = HashMultimap.create(); + + for (ObjectName objectName : instancesToMap) { + String factoryName = ObjectNameUtil.getFactoryName(objectName); + retVal.put(factoryName, objectName); + } + return retVal; + } + + // public Element toXml(Set instancesToMap, String namespace, + // Document document) { + // return toXml(instancesToMap, Optional.of(namespace), document); + // } + + public Element toXml(Set instancesToMap, Optional maybeNamespace, Document document, + Element dataElement) { + Services serviceTracker = new Services(); + + Map>> moduleToInstances = getMappedInstances(instancesToMap, + serviceTracker); + + Element root = dataElement; + if (maybeNamespace.isPresent()) { + XmlUtil.addNamespaceAttr(root, maybeNamespace.get()); + } + + Element modulesElement = document.createElement(XmlNetconfConstants.MODULES_KEY); + XmlUtil.addNamespaceAttr(modulesElement, + XmlNetconfConstants.URN_OPENDAYLIGHT_PARAMS_XML_NS_YANG_CONTROLLER_CONFIG); + root.appendChild(modulesElement); + for (String moduleNamespace : moduleToInstances.keySet()) { + for (Entry> moduleMappingEntry : moduleToInstances.get(moduleNamespace) + .entrySet()) { + + ModuleConfig mapping = moduleConfigs.get(moduleNamespace).get(moduleMappingEntry.getKey()); + + if (moduleMappingEntry.getValue().isEmpty()) { + addEmptyModulesCommented(document, modulesElement, moduleNamespace, moduleMappingEntry); + } else { + for (ObjectName objectName : moduleMappingEntry.getValue()) { + modulesElement + .appendChild(mapping.toXml(objectName, serviceTracker, document, moduleNamespace)); + } + } + + } + } + + root.appendChild(serviceTracker.toXml(serviceTracker.getMappedServices(), document)); + + return root; + } + + private void addEmptyModulesCommented(Document document, Element root, String moduleNamespace, + Entry> moduleMappingEntry) { + Element emptyModule = document.createElement(XmlNetconfConstants.MODULE_KEY); + + Element typeElement = XmlUtil.createTextElement(document, XmlNetconfConstants.TYPE_KEY, + moduleMappingEntry.getKey()); + emptyModule.appendChild(typeElement); + + root.appendChild(document.createComment(XmlUtil.toString(emptyModule, false))); + } + + // TODO refactor, replace string representing namespace with namespace class + // TODO refactor, replace Map->Multimap with e.g. ConfigElementResolved + // class + public Map> fromXml(XmlElement xml) { + Map> retVal = Maps.newHashMap(); + + List recognisedChildren = Lists.newArrayList(); + + Services serviceTracker = fromXmlServices(xml, recognisedChildren); + List moduleElements = fromXmlModules(xml, recognisedChildren); + + xml.checkUnrecognisedElements(recognisedChildren); + + for (XmlElement moduleElement : moduleElements) { + resolveModule(retVal, serviceTracker, moduleElement); + } + + return retVal; + } + + private List fromXmlModules(XmlElement xml, List recognisedChildren) { + Optional modulesElement = xml.getOnlyChildElementOptionally(XmlNetconfConstants.MODULES_KEY, + XmlNetconfConstants.URN_OPENDAYLIGHT_PARAMS_XML_NS_YANG_CONTROLLER_CONFIG); + List moduleElements; + if (modulesElement.isPresent()) { + moduleElements = modulesElement.get().getChildElementsWithSameNamespace(XmlNetconfConstants.MODULE_KEY); + recognisedChildren.add(modulesElement.get()); + modulesElement.get().checkUnrecognisedElements(moduleElements); + } else { + moduleElements = Lists.newArrayList(); + } + return moduleElements; + } + + private void resolveModule(Map> retVal, Services serviceTracker, + XmlElement moduleElement) { + XmlElement typeElement = moduleElement.getOnlyChildElementWithSameNamespace(XmlNetconfConstants.TYPE_KEY); + Entry prefixToNamespace = typeElement.findNamespaceOfTextContent(); + String moduleNamespace = prefixToNamespace.getValue(); + XmlElement nameElement = moduleElement.getOnlyChildElementWithSameNamespace(XmlNetconfConstants.NAME_KEY); + String instanceName = nameElement.getTextContent(); + String factoryNameWithPrefix = typeElement.getTextContent(); + String prefixOrEmptyString = prefixToNamespace.getKey(); + String factoryName = getFactoryName(factoryNameWithPrefix, prefixOrEmptyString); + + ModuleConfig moduleMapping = getModuleMapping(moduleNamespace, instanceName, factoryName); + + Multimap innerMap = retVal.get(moduleNamespace); + if (innerMap == null) { + innerMap = HashMultimap.create(); + retVal.put(moduleNamespace, innerMap); + } + + ModuleElementResolved moduleElementResolved = moduleMapping.fromXml(moduleElement, serviceTracker, + instanceName, moduleNamespace); + + innerMap.put(factoryName, moduleElementResolved); + } + + private Services fromXmlServices(XmlElement xml, List recognisedChildren) { + Optional servicesElement = xml.getOnlyChildElementOptionally(XmlNetconfConstants.SERVICES_KEY, + XmlNetconfConstants.URN_OPENDAYLIGHT_PARAMS_XML_NS_YANG_CONTROLLER_CONFIG); + + Map> mappedServices; + if (servicesElement.isPresent()) { + mappedServices = Services.fromXml(servicesElement.get()); + recognisedChildren.add(servicesElement.get()); + } else { + mappedServices = new HashMap<>(); + } + + return Services.resolveServices(mappedServices); + } + + private String getFactoryName(String factoryNameWithPrefix, String prefixOrEmptyString) { + checkState( + factoryNameWithPrefix.startsWith(prefixOrEmptyString), + format("Internal error: text " + "content '%s' of type node does not start with prefix '%s'", + factoryNameWithPrefix, prefixOrEmptyString)); + + int factoryNameAfterPrefixIndex; + if (prefixOrEmptyString.isEmpty()) { + factoryNameAfterPrefixIndex = 0; + } else { + factoryNameAfterPrefixIndex = prefixOrEmptyString.length() + 1; + } + return factoryNameWithPrefix.substring(factoryNameAfterPrefixIndex); + } + + private ModuleConfig getModuleMapping(String moduleNamespace, String instanceName, String factoryName) { + Map mappingsFromNamespace = moduleConfigs.get(moduleNamespace); + + Preconditions.checkNotNull(mappingsFromNamespace, + "Namespace %s, defined in: module %s of type %s not found, available namespaces: %s", moduleNamespace, + instanceName, factoryName, moduleConfigs.keySet()); + + ModuleConfig moduleMapping = mappingsFromNamespace.get(factoryName); + checkState(moduleMapping != null, "Cannot find mapping for module type " + factoryName); + return moduleMapping; + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/config/InstanceConfig.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/config/InstanceConfig.java new file mode 100644 index 0000000000..d1ba0b0206 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/config/InstanceConfig.java @@ -0,0 +1,196 @@ +/* + * 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.netconf.confignetconfconnector.mapping.config; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.opendaylight.controller.config.util.ConfigRegistryClient; +import org.opendaylight.controller.config.yangjmxgenerator.RuntimeBeanEntry; +import org.opendaylight.controller.config.yangjmxgenerator.attribute.AttributeIfc; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.fromxml.AttributeConfigElement; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.fromxml.AttributeReadingStrategy; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.fromxml.ObjectXmlReader; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.mapping.AttributeMappingStrategy; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.mapping.ObjectMapper; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.resolving.AttributeResolvingStrategy; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.resolving.ObjectResolver; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.toxml.AttributeWritingStrategy; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.toxml.ObjectXmlWriter; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.management.ObjectName; +import javax.management.openmbean.OpenType; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +public final class InstanceConfig { + private static final Logger logger = LoggerFactory.getLogger(InstanceConfig.class); + + private final Map yangToAttrConfig; + private final Map jmxToAttrConfig; + private final ConfigRegistryClient configRegistryClient; + + public InstanceConfig(ConfigRegistryClient configRegistryClient, Map yangNamesToAttributes) { + this.yangToAttrConfig = yangNamesToAttributes; + this.jmxToAttrConfig = reverseMap(yangNamesToAttributes); + this.configRegistryClient = configRegistryClient; + } + + private Map getMappedConfiguration(ObjectName on, Services depTracker) { + + // TODO make field, mappingStrategies can be instantiated only once + Map>> mappingStrategies = new ObjectMapper(depTracker) + .prepareMapping(jmxToAttrConfig); + + Map toXml = Maps.newHashMap(); + + for (Entry configDefEntry : jmxToAttrConfig.entrySet()) { + + // Skip children runtime beans as they are mapped by InstanceRuntime + if (configDefEntry.getValue() instanceof RuntimeBeanEntry) + continue; + + Object value = configRegistryClient.getAttributeCurrentValue(on, configDefEntry.getKey()); + try { + AttributeMappingStrategy> attributeMappingStrategy = mappingStrategies + .get(configDefEntry.getKey()); + Optional a = attributeMappingStrategy.mapAttribute(value); + if (a.isPresent() == false) + continue; + + toXml.put(configDefEntry.getValue().getAttributeYangName(), a.get()); + } catch (Exception e) { + throw new IllegalStateException("Unable to map value " + value + " to attribute " + + configDefEntry.getKey(), e); + } + } + + return toXml; + } + + public Element toXml(ObjectName on, Services depTracker, String namespace, Document document, Element rootElement) { + + Element cfgElement = rootElement; + + Map strats = new ObjectXmlWriter().prepareWriting(yangToAttrConfig, document); + + Map mappedConfig = getMappedConfiguration(on, depTracker); + + for (Entry mappingEntry : mappedConfig.entrySet()) { + try { + strats.get(mappingEntry.getKey()).writeElement(cfgElement, namespace, mappingEntry.getValue()); + } catch (Exception e) { + throw new IllegalStateException("Unable to write value " + mappingEntry.getValue() + " for attribute " + + mappingEntry.getValue(), e); + } + } + + return cfgElement; + } + + private void resolveConfiguration(InstanceConfigElementResolved mappedConfig, Services depTracker) { + + // TODO make field, resolvingStrategies can be instantiated only once + Map>> resolvingStrategies = new ObjectResolver( + depTracker).prepareResolving(yangToAttrConfig); + + for (Entry configDefEntry : mappedConfig.getConfiguration().entrySet()) { + try { + + AttributeResolvingStrategy> attributeResolvingStrategy = resolvingStrategies + .get(configDefEntry.getKey()); + + configDefEntry.getValue().resolveValue(attributeResolvingStrategy, configDefEntry.getKey()); + configDefEntry.getValue().setJmxName( + yangToAttrConfig.get(configDefEntry.getKey()).getUpperCaseCammelCase()); + } catch (Exception e) { + throw new IllegalStateException("Unable to resolve value " + configDefEntry.getValue() + + " to attribute " + configDefEntry.getKey(), e); + } + } + } + + public InstanceConfigElementResolved fromXml(XmlElement moduleElement, Services services, String moduleNamespace) { + Map retVal = Maps.newHashMap(); + + Map strats = new ObjectXmlReader().prepareReading(yangToAttrConfig); + List recognisedChildren = Lists.newArrayList(); + + XmlElement type = moduleElement.getOnlyChildElementWithSameNamespace(XmlNetconfConstants.TYPE_KEY); + XmlElement name = moduleElement.getOnlyChildElementWithSameNamespace(XmlNetconfConstants.NAME_KEY); + List typeAndName = Lists.newArrayList(type, name); + + for (Entry readStratEntry : strats.entrySet()) { + List configNodes = getConfigNodes(moduleElement, moduleNamespace, readStratEntry.getKey(), + recognisedChildren, typeAndName); + AttributeConfigElement readElement = readStratEntry.getValue().readElement(configNodes); + retVal.put(readStratEntry.getKey(), readElement); + } + + recognisedChildren.addAll(typeAndName); + moduleElement.checkUnrecognisedElements(recognisedChildren); + + // TODO: add check for conflicts between global and local edit strategy + String perInstanceEditStrategy = moduleElement.getAttribute(XmlNetconfConstants.OPERATION_ATTR_KEY, + XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0); + + InstanceConfigElementResolved instanceConfigElementResolved = perInstanceEditStrategy.equals("") ? new InstanceConfigElementResolved( + retVal) : new InstanceConfigElementResolved(perInstanceEditStrategy, retVal); + + resolveConfiguration(instanceConfigElementResolved, services); + return instanceConfigElementResolved; + } + + private List getConfigNodes(XmlElement moduleElement, String moduleNamespace, String name, + List recognisedChildren, List typeAndName) { + List foundConfigNodes = moduleElement.getChildElementsWithinNamespace(name, moduleNamespace); + if (foundConfigNodes.isEmpty()) { + logger.debug("No config nodes {}:{} found in {}", moduleNamespace, name, moduleElement); + logger.debug("Trying lookup of config nodes without specified namespace"); + foundConfigNodes = moduleElement.getChildElementsWithinNamespace(name, + XmlNetconfConstants.URN_OPENDAYLIGHT_PARAMS_XML_NS_YANG_CONTROLLER_CONFIG); + // In case module type or name element is not present in config it + // would be matched with config type or name + // We need to remove config type and name from available module + // config elements + foundConfigNodes.removeAll(typeAndName); + logger.debug("Found {} config nodes {} without specified namespace in {}", foundConfigNodes.size(), name, + moduleElement); + } else { + List foundWithoutNamespaceNodes = moduleElement.getChildElementsWithinNamespace(name, + XmlNetconfConstants.URN_OPENDAYLIGHT_PARAMS_XML_NS_YANG_CONTROLLER_CONFIG); + foundWithoutNamespaceNodes.removeAll(typeAndName); + Preconditions.checkState(foundWithoutNamespaceNodes.isEmpty(), + "Element %s present multiple times with different namespaces: %s, %s", name, foundConfigNodes, + foundWithoutNamespaceNodes); + } + + recognisedChildren.addAll(foundConfigNodes); + return foundConfigNodes; + } + + private static Map reverseMap(Map yangNameToAttr) { + Map reversednameToAtr = Maps.newHashMap(); + + for (Entry entry : yangNameToAttr.entrySet()) { + reversednameToAtr.put(entry.getValue().getUpperCaseCammelCase(), entry.getValue()); + } + + return reversednameToAtr; + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/config/InstanceConfigElementResolved.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/config/InstanceConfigElementResolved.java new file mode 100644 index 0000000000..6624fc182e --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/config/InstanceConfigElementResolved.java @@ -0,0 +1,61 @@ +/* + * 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.netconf.confignetconfconnector.mapping.config; + +import com.google.common.base.Preconditions; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.fromxml.AttributeConfigElement; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.editconfig.EditConfigStrategy; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.editconfig.EditConfigXmlParser; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.editconfig.EditStrategyType; + +import java.util.Map; + +/** + * Parsed xml element containing whole configuration for an instance of some + * module. Contains preferred edit strategy type. + */ +public class InstanceConfigElementResolved { + + private final EditStrategyType editStrategy; + private final Map configuration; + + public InstanceConfigElementResolved(String strat, Map configuration) { + EditStrategyType valueOf = checkStrategy(strat); + this.editStrategy = valueOf; + this.configuration = configuration; + } + + EditStrategyType checkStrategy(String strat) { + EditStrategyType valueOf = EditStrategyType.valueOf(strat); + if (EditStrategyType.defaultStrategy().isEnforcing()) { + Preconditions + .checkArgument( + valueOf == EditStrategyType.defaultStrategy(), + "With " + + EditStrategyType.defaultStrategy() + + " as " + + EditConfigXmlParser.DEFAULT_OPERATION_KEY + + " operations on module elements are not permitted since the default option is restrictive"); + } + return valueOf; + } + + public InstanceConfigElementResolved(Map configuration) { + editStrategy = EditStrategyType.defaultStrategy(); + this.configuration = configuration; + } + + public EditConfigStrategy getEditStrategy() { + return editStrategy.getFittingStrategy(); + } + + public Map getConfiguration() { + return configuration; + } +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/config/ModuleConfig.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/config/ModuleConfig.java new file mode 100644 index 0000000000..3a0c54754b --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/config/ModuleConfig.java @@ -0,0 +1,84 @@ +/* + * 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.netconf.confignetconfconnector.mapping.config; + +import org.opendaylight.controller.config.api.jmx.ObjectNameUtil; +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.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.management.ObjectName; +import java.util.Collection; +import java.util.Collections; + +public class ModuleConfig { + + private final String moduleName; + private final InstanceConfig instanceConfig; + private final Collection providedServices; + + public ModuleConfig(String moduleName, InstanceConfig mbeanMapping, Collection providedServices) { + this.moduleName = moduleName; + this.instanceConfig = mbeanMapping; + this.providedServices = providedServices; + } + + public ModuleConfig(String key, InstanceConfig instanceConfig) { + this(key, instanceConfig, Collections. emptyList()); + } + + public InstanceConfig getMbeanMapping() { + return instanceConfig; + } + + public Collection getProvidedServices() { + return providedServices; + } + + public Element toXml(ObjectName instanceON, Services depTracker, Document document, String namespace) { + Element root = document.createElement(XmlNetconfConstants.MODULE_KEY); + // Xml.addNamespaceAttr(document, root, namespace); + + final String prefix = getPrefix(namespace); + Element typeElement = XmlUtil.createPrefixedTextElement(document, XmlNetconfConstants.TYPE_KEY, prefix, + moduleName); + XmlUtil.addPrefixedNamespaceAttr(typeElement, prefix, namespace); + // Xml.addNamespaceAttr(document, typeElement, + // XMLUtil.URN_OPENDAYLIGHT_PARAMS_XML_NS_YANG_CONTROLLER_CONFIG); + root.appendChild(typeElement); + + Element nameElement = XmlUtil.createTextElement(document, XmlNetconfConstants.NAME_KEY, + ObjectNameUtil.getInstanceName(instanceON)); + // Xml.addNamespaceAttr(document, nameElement, + // XMLUtil.URN_OPENDAYLIGHT_PARAMS_XML_NS_YANG_CONTROLLER_CONFIG); + root.appendChild(nameElement); + + root = instanceConfig.toXml(instanceON, depTracker, namespace, document, root); + + return root; + } + + private String getPrefix(String namespace) { + // if(namespace.contains(":")==false) + return "prefix"; + // return namespace.substring(namespace.lastIndexOf(':') + 1, + // namespace.length()); + + } + + public ModuleElementResolved fromXml(XmlElement moduleElement, Services depTracker, String instanceName, + String moduleNamespace) { + + InstanceConfigElementResolved ice = instanceConfig.fromXml(moduleElement, depTracker, moduleNamespace); + return new ModuleElementResolved(instanceName, ice); + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/config/ModuleElementResolved.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/config/ModuleElementResolved.java new file mode 100644 index 0000000000..6d2936c827 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/config/ModuleElementResolved.java @@ -0,0 +1,29 @@ +/* + * 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.netconf.confignetconfconnector.mapping.config; + +public class ModuleElementResolved { + + private final String instanceName; + private final InstanceConfigElementResolved instanceConfigElementResolved; + + public ModuleElementResolved(String instanceName, InstanceConfigElementResolved instanceConfigElementResolved) { + this.instanceName = instanceName; + this.instanceConfigElementResolved = instanceConfigElementResolved; + } + + public String getInstanceName() { + return instanceName; + } + + public InstanceConfigElementResolved getInstanceConfigElementResolved() { + return instanceConfigElementResolved; + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/config/Services.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/config/Services.java new file mode 100644 index 0000000000..7e4b6c2c06 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/config/Services.java @@ -0,0 +1,311 @@ +/* + * 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.netconf.confignetconfconnector.mapping.config; + +import com.google.common.base.Function; +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +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.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.annotation.Nullable; +import javax.management.ObjectName; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class Services { + + private static final String PROVIDER_KEY = "provider"; + private static final String NAME_KEY = "name"; + public static final String TYPE_KEY = "type"; + public static final String SERVICE_KEY = "service"; + + private long suffix = 1; + + private final Map instanceToRef = Maps.newHashMap(); + private final Map> serviceNameToRefNameToInstance = Maps + .newHashMap(); + + public String addServiceEntry(String serviceName, ObjectName on) { + + String moduleName = on.getKeyProperty("moduleFactoryName"); + String instanceName = on.getKeyProperty("instanceName"); + + return addServiceEntry(serviceName, moduleName, instanceName); + } + + public String addServiceEntry(String serviceName, String moduleName, String instanceName) { + ServiceInstance serviceInstance = new ServiceInstance(moduleName, instanceName); + serviceInstance.setServiceName(serviceName); + + String refName = instanceToRef.get(serviceInstance); + + Map refNameToInstance = serviceNameToRefNameToInstance.get(serviceName); + if (refNameToInstance == null) { + refNameToInstance = Maps.newHashMap(); + serviceNameToRefNameToInstance.put(serviceName, refNameToInstance); + } + + if (refName != null) { + if (serviceNameToRefNameToInstance.get(serviceName).containsKey(moduleName) == false) { + refNameToInstance.put(refName, serviceInstance); + } + return refName; + } else { + refName = "ref_" + instanceName; + + final Set refNamesAsSet = toSet(instanceToRef.values()); + if (refNamesAsSet.contains(refName)) { + refName = findAvailableRefName(refName, refNamesAsSet); + } + + instanceToRef.put(serviceInstance, refName); + refNameToInstance.put(refName, serviceInstance); + + return refName; + } + } + + private Set toSet(Collection values) { + Set refNamesAsSet = Sets.newHashSet(); + + for (String refName : values) { + boolean resultAdd = refNamesAsSet.add(refName); + Preconditions.checkState(resultAdd, + "Error occurred building services element, reference name {} was present twice", refName); + } + + return refNamesAsSet; + } + + public ServiceInstance getByServiceAndRefName(String serviceName, String refName) { + Map refNameToInstance = serviceNameToRefNameToInstance.get(serviceName); + Preconditions.checkArgument(refNameToInstance != null, "No serviceInstances mapped to " + serviceName + " , " + + serviceNameToRefNameToInstance.keySet()); + + ServiceInstance serviceInstance = refNameToInstance.get(refName); + Preconditions.checkArgument(serviceInstance != null, "No serviceInstance mapped to " + refName + + " under service name " + serviceName + " , " + refNameToInstance.keySet()); + return serviceInstance; + } + + // TODO hide getMappedServices, call it explicitly in toXml + + public Map> getMappedServices() { + Map> retVal = Maps.newHashMap(); + + for (String serviceName : serviceNameToRefNameToInstance.keySet()) { + + Map innerRetVal = Maps.transformValues(serviceNameToRefNameToInstance.get(serviceName), + new Function() { + @Nullable + @Override + public String apply(@Nullable ServiceInstance serviceInstance) { + return serviceInstance.toString(); + } + }); + retVal.put(serviceName, innerRetVal); + } + + return retVal; + } + + // TODO hide resolveServices, call it explicitly in fromXml + + public static Services resolveServices(Map> mappedServices) { + Services tracker = new Services(); + + for (Entry> serviceEntry : mappedServices.entrySet()) { + + String serviceName = serviceEntry.getKey(); + for (Entry refEntry : serviceEntry.getValue().entrySet()) { + + Map refNameToInstance = tracker.serviceNameToRefNameToInstance + .get(serviceName); + if (refNameToInstance == null) { + refNameToInstance = Maps.newHashMap(); + tracker.serviceNameToRefNameToInstance.put(serviceName, refNameToInstance); + } + + String refName = refEntry.getKey(); + Preconditions.checkState(false == refNameToInstance.containsKey(refName), + "Duplicate reference name to service " + refName + " under service " + serviceName); + ServiceInstance serviceInstance = ServiceInstance.fromString(refEntry.getValue()); + refNameToInstance.put(refName, serviceInstance); + + tracker.instanceToRef.put(serviceInstance, refEntry.getKey()); + } + } + return tracker; + } + + public static Map> fromXml(XmlElement xml) { + Map> retVal = Maps.newHashMap(); + + List services = xml.getChildElements(SERVICE_KEY); + xml.checkUnrecognisedElements(services); + + for (XmlElement service : services) { + + XmlElement typeElement = service.getOnlyChildElement(TYPE_KEY); + String serviceName = typeElement.getTextContent(); + + Map innerMap = Maps.newHashMap(); + retVal.put(serviceName, innerMap); + + List instances = service.getChildElements(XmlNetconfConstants.INSTANCE_KEY); + service.checkUnrecognisedElements(instances, typeElement); + + for (XmlElement instance : instances) { + XmlElement nameElement = instance.getOnlyChildElement(NAME_KEY); + String refName = nameElement.getTextContent(); + + XmlElement providerElement = instance.getOnlyChildElement(PROVIDER_KEY); + String providerName = providerElement.getTextContent(); + + instance.checkUnrecognisedElements(nameElement, providerElement); + + innerMap.put(refName, providerName); + } + } + + return retVal; + } + + private String findAvailableRefName(String refName, Set refNamesAsSet) { + String intitialRefName = refName; + + while (true) { + refName = intitialRefName + "_" + suffix++; + if (refNamesAsSet.contains(refName) == false) + return refName; + } + } + + public Element toXml(Map> mappedServices, Document document) { + Element root = document.createElement(XmlNetconfConstants.SERVICES_KEY); + XmlUtil.addNamespaceAttr(root, XmlNetconfConstants.URN_OPENDAYLIGHT_PARAMS_XML_NS_YANG_CONTROLLER_CONFIG); + + for (Entry> serviceEntry : mappedServices.entrySet()) { + Element serviceElement = document.createElement(SERVICE_KEY); + root.appendChild(serviceElement); + + Element typeElement = XmlUtil.createTextElement(document, TYPE_KEY, serviceEntry.getKey()); + serviceElement.appendChild(typeElement); + + for (Entry instanceEntry : serviceEntry.getValue().entrySet()) { + Element instanceElement = document.createElement(XmlNetconfConstants.INSTANCE_KEY); + serviceElement.appendChild(instanceElement); + + Element nameElement = XmlUtil.createTextElement(document, NAME_KEY, instanceEntry.getKey()); + instanceElement.appendChild(nameElement); + + Element providerElement = XmlUtil.createTextElement(document, PROVIDER_KEY, instanceEntry.getValue()); + instanceElement.appendChild(providerElement); + } + } + + return root; + } + + public static final class ServiceInstance { + public ServiceInstance(String moduleName, String instanceName) { + this.moduleName = moduleName; + this.instanceName = instanceName; + } + + public static ServiceInstance fromString(String instanceId) { + instanceId = instanceId.trim(); + Matcher matcher = p.matcher(instanceId); + Preconditions.checkArgument(matcher.matches(), "Unexpected format for provider, expected " + p.toString() + + " but was " + instanceId); + String factoryName = matcher.group(1); + String instanceName = matcher.group(2); + return new ServiceInstance(factoryName, instanceName); + } + + private final String moduleName, instanceName; + private String serviceName; + + public String getServiceName() { + return serviceName; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + public String getModuleName() { + return moduleName; + } + + public String getInstanceName() { + return instanceName; + } + + private static final String blueprint = "/" + XmlNetconfConstants.CONFIG_KEY + "/" + + XmlNetconfConstants.MODULES_KEY + "/" + XmlNetconfConstants.MODULE_KEY + "[" + + XmlNetconfConstants.NAME_KEY + "='%s']/" + XmlNetconfConstants.INSTANCE_KEY + "[" + + XmlNetconfConstants.NAME_KEY + "='%s']"; + + private static final String blueprintR = "/" + XmlNetconfConstants.CONFIG_KEY + "/" + + XmlNetconfConstants.MODULES_KEY + "/" + XmlNetconfConstants.MODULE_KEY + "\\[" + + XmlNetconfConstants.NAME_KEY + "='%s'\\]/" + XmlNetconfConstants.INSTANCE_KEY + "\\[" + + XmlNetconfConstants.NAME_KEY + "='%s'\\]"; + + private static final Pattern p = Pattern.compile(String.format(blueprintR, "(.+)", "(.+)")); + + @Override + public String toString() { + return String.format(blueprint, moduleName, instanceName); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((instanceName == null) ? 0 : instanceName.hashCode()); + result = prime * result + ((moduleName == null) ? 0 : moduleName.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ServiceInstance other = (ServiceInstance) obj; + if (instanceName == null) { + if (other.instanceName != null) + return false; + } else if (!instanceName.equals(other.instanceName)) + return false; + if (moduleName == null) { + if (other.moduleName != null) + return false; + } else if (!moduleName.equals(other.moduleName)) + return false; + return true; + } + + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/rpc/InstanceRuntimeRpc.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/rpc/InstanceRuntimeRpc.java new file mode 100644 index 0000000000..e91357e47a --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/rpc/InstanceRuntimeRpc.java @@ -0,0 +1,90 @@ +/* + * 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.netconf.confignetconfconnector.mapping.rpc; + +import com.google.common.collect.Maps; +import org.opendaylight.controller.config.yangjmxgenerator.RuntimeBeanEntry.Rpc; +import org.opendaylight.controller.config.yangjmxgenerator.attribute.AttributeIfc; +import org.opendaylight.controller.config.yangjmxgenerator.attribute.JavaAttribute; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.fromxml.AttributeConfigElement; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.fromxml.AttributeReadingStrategy; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.fromxml.ObjectXmlReader; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.resolving.AttributeResolvingStrategy; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.resolving.ObjectResolver; +import org.opendaylight.controller.netconf.util.xml.XmlElement; + +import javax.management.openmbean.OpenType; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +public final class InstanceRuntimeRpc { + + private final Map yangToAttrConfig; + private final Rpc rpc; + + public InstanceRuntimeRpc(Rpc rpc) { + this.yangToAttrConfig = map(rpc.getParameters()); + this.rpc = rpc; + } + + private Map map(List parameters) { + Map mapped = Maps.newHashMap(); + for (JavaAttribute javaAttribute : parameters) { + mapped.put(javaAttribute.getAttributeYangName(), javaAttribute); + } + return mapped; + } + + private void resolveConfiguration(Map mappedConfig) { + + // TODO make field, resolvingStrategies can be instantiated only once + Map>> resolvingStrategies = new ObjectResolver(null) + .prepareResolving(yangToAttrConfig); + // TODO make constructor for object resolver without service tracker + for (Entry configDefEntry : mappedConfig.entrySet()) { + try { + + AttributeResolvingStrategy> attributeResolvingStrategy = resolvingStrategies + .get(configDefEntry.getKey()); + + configDefEntry.getValue().resolveValue(attributeResolvingStrategy, configDefEntry.getKey()); + configDefEntry.getValue().setJmxName( + yangToAttrConfig.get(configDefEntry.getKey()).getUpperCaseCammelCase()); + } catch (Exception e) { + throw new IllegalStateException("Unable to resolve value " + configDefEntry.getValue() + + " to attribute " + configDefEntry.getKey(), e); + } + } + } + + public Map fromXml(XmlElement configRootNode) { + Map retVal = Maps.newHashMap(); + + Map strats = new ObjectXmlReader().prepareReading(yangToAttrConfig); + + for (Entry readStratEntry : strats.entrySet()) { + List configNodes = configRootNode.getChildElements(readStratEntry.getKey()); + AttributeConfigElement readElement = readStratEntry.getValue().readElement(configNodes); + retVal.put(readStratEntry.getKey(), readElement); + } + + resolveConfiguration(retVal); + return retVal; + } + + public String getName() { + return rpc.getName(); + } + + public String getReturnType() { + return rpc.getReturnType(); + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/rpc/ModuleRpcs.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/rpc/ModuleRpcs.java new file mode 100644 index 0000000000..a081890d39 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/rpc/ModuleRpcs.java @@ -0,0 +1,58 @@ +/* + * 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.netconf.confignetconfconnector.mapping.rpc; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import org.opendaylight.controller.config.yangjmxgenerator.RuntimeBeanEntry; +import org.opendaylight.controller.config.yangjmxgenerator.RuntimeBeanEntry.Rpc; + +import java.util.Map; + +public final class ModuleRpcs { + + Map yangToJavaNames = Maps.newHashMap(); + Map> rpcMapping = Maps.newHashMap(); + + public void addNameMapping(RuntimeBeanEntry runtimeEntry) { + String yangName = runtimeEntry.getYangName(); + Preconditions.checkState(yangToJavaNames.containsKey(yangName) == false, + "RuntimeBean %s found twice in same namespace", yangName); + yangToJavaNames.put(yangName, runtimeEntry.getJavaNamePrefix()); + } + + public void addRpc(RuntimeBeanEntry runtimeEntry, Rpc rpc) { + String yangName = runtimeEntry.getYangName(); + Map map = rpcMapping.get(yangName); + if (map == null) { + map = Maps.newHashMap(); + rpcMapping.put(yangName, map); + } + + Preconditions.checkState(map.containsKey(rpc.getYangName()) == false, "Rpc %s for runtime bean %s added twice", + rpc.getYangName(), yangName); + map.put(rpc.getYangName(), new InstanceRuntimeRpc(rpc)); + } + + public String getRbeJavaName(String yangName) { + String javaName = yangToJavaNames.get(yangName); + Preconditions.checkState(javaName != null, + "No runtime bean entry found under yang name %s, available yang names %s", yangName, + yangToJavaNames.keySet()); + return javaName; + } + + public InstanceRuntimeRpc getRpc(String rbeName, String rpcName) { + Map rpcs = rpcMapping.get(rbeName); + Preconditions.checkState(rpcs != null, "No rpcs found for runtime bean %s", rbeName); + InstanceRuntimeRpc rpc = rpcs.get(rpcName); + Preconditions.checkState(rpc != null, "No rpc found for runtime bean %s with name %s", rbeName, rpcName); + return rpc; + } +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/rpc/Rpcs.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/rpc/Rpcs.java new file mode 100644 index 0000000000..556541ab22 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/rpc/Rpcs.java @@ -0,0 +1,33 @@ +/* + * 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.netconf.confignetconfconnector.mapping.rpc; + +import com.google.common.base.Preconditions; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.runtimerpc.RuntimeRpcElementResolved; + +import java.util.Map; + +public class Rpcs { + private final Map> mappedRpcs; + + public Rpcs(Map> mappedRpcs) { + super(); + this.mappedRpcs = mappedRpcs; + } + + public ModuleRpcs getRpcMapping(RuntimeRpcElementResolved id) { + Map modules = mappedRpcs.get(id.getNamespace()); + Preconditions.checkState(modules != null, "No modules found for namespace %s", id.getNamespace()); + ModuleRpcs rpcMapping = modules.get(id.getModuleName()); + Preconditions.checkState(modules != null, "No module %s found for namespace %s", id.getModuleName(), + id.getNamespace()); + + return rpcMapping; + } +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/runtime/InstanceRuntime.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/runtime/InstanceRuntime.java new file mode 100644 index 0000000000..9d348d0985 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/runtime/InstanceRuntime.java @@ -0,0 +1,117 @@ +/* + * 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.netconf.confignetconfconnector.mapping.runtime; + +import com.google.common.base.Predicate; +import com.google.common.collect.Collections2; +import com.google.common.collect.Sets; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config.InstanceConfig; +import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.management.ObjectName; +import java.util.Hashtable; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public class InstanceRuntime { + + /** + * + */ + private static final String KEY_ATTRIBUTE_KEY = "key"; + + private final InstanceConfig instanceMapping; + private final Map childrenMappings; + private final Map jmxToYangChildRbeMapping; + + public InstanceRuntime(InstanceConfig instanceMapping, Map childrenMappings, + Map jmxToYangChildRbeMapping) { + this.instanceMapping = instanceMapping; + this.childrenMappings = childrenMappings; + this.jmxToYangChildRbeMapping = jmxToYangChildRbeMapping; + } + + /** + * Finds all children runtime beans, same properties and values as current + * root + any number of additional properties + */ + private Set findChildren(ObjectName innerRootBean, Set childRbeOns) { + final Hashtable wantedProperties = innerRootBean.getKeyPropertyList(); + + return Sets.newHashSet(Collections2.filter(childRbeOns, new Predicate() { + + @Override + public boolean apply(ObjectName on) { + Hashtable localProperties = on.getKeyPropertyList(); + for (Entry propertyEntry : wantedProperties.entrySet()) { + if (!localProperties.containsKey(propertyEntry.getKey())) + return false; + if (!localProperties.get(propertyEntry.getKey()).equals(propertyEntry.getValue())) + return false; + if (localProperties.size() <= wantedProperties.size()) + return false; + } + return true; + } + })); + } + + /** + * Finds next level root runtime beans, beans that have the same properties + * as current root + one additional + */ + private Set getRootBeans(Set childRbeOns, final String string, final int keyListSize) { + return Sets.newHashSet(Collections2.filter(childRbeOns, new Predicate() { + + @Override + public boolean apply(ObjectName on) { + if (on.getKeyPropertyList().size() != keyListSize + 1) + return false; + if (!on.getKeyPropertyList().containsKey(string)) + return false; + return true; + } + })); + } + + public Element toXml(ObjectName rootOn, Set childRbeOns, Document document) { + return toXml(rootOn, childRbeOns, document, null, null); + } + + public Element toXml(ObjectName rootOn, Set childRbeOns, Document document, String instanceIndex, + String keyName) { + Element xml = document.createElement(keyName == null ? XmlNetconfConstants.DATA_KEY : keyName); + // TODO namespace + xml = instanceMapping.toXml(rootOn, null, "namespace", document, xml); + + if (instanceIndex != null) { + xml.setAttribute(KEY_ATTRIBUTE_KEY, instanceIndex); + } + + for (Entry childMappingEntry : childrenMappings.entrySet()) { + Set innerRootBeans = getRootBeans(childRbeOns, childMappingEntry.getKey(), rootOn + .getKeyPropertyList().size()); + + for (ObjectName objectName : innerRootBeans) { + Set innerChildRbeOns = findChildren(objectName, childRbeOns); + String runtimeInstanceIndex = objectName.getKeyProperty(childMappingEntry.getKey()); + + String elementName = jmxToYangChildRbeMapping.get(childMappingEntry.getKey()); + xml.appendChild(childMappingEntry.getValue().toXml(objectName, innerChildRbeOns, document, + runtimeInstanceIndex, elementName)); + } + } + + return xml; + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/runtime/ModuleRuntime.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/runtime/ModuleRuntime.java new file mode 100644 index 0000000000..07da65ed19 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/runtime/ModuleRuntime.java @@ -0,0 +1,71 @@ +/* + * 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.netconf.confignetconfconnector.mapping.runtime; + +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; +import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.management.ObjectName; +import java.util.Collection; +import java.util.Set; + +public class ModuleRuntime { + + private final String moduleName; + private final InstanceRuntime instanceRuntime; + + public ModuleRuntime(String moduleName, InstanceRuntime instanceRuntime) { + this.moduleName = moduleName; + this.instanceRuntime = instanceRuntime; + } + + public InstanceRuntime getMbeanMapping() { + return instanceRuntime; + } + + private ObjectName findRoot(Collection runtimeBeanOns) { + for (ObjectName objectName : runtimeBeanOns) { + if (objectName.getKeyPropertyList().size() == 3) + return objectName; + } + throw new IllegalStateException("Root runtime bean not found among " + runtimeBeanOns); + } + + public Element toXml(String namespace, Multimap instances, Document document) { + Element root = document.createElement(XmlNetconfConstants.MODULE_KEY); + XmlUtil.addNamespaceAttr(root, namespace); + + Element nameElement = XmlUtil.createTextElement(document, XmlNetconfConstants.NAME_KEY, moduleName); + root.appendChild(nameElement); + + for (String instanceName : instances.keySet()) { + Element instance = document.createElement(XmlNetconfConstants.INSTANCE_KEY); + + Element innerNameElement = XmlUtil.createTextElement(document, XmlNetconfConstants.NAME_KEY, instanceName); + instance.appendChild(innerNameElement); + + Collection runtimeBeanOns = instances.get(instanceName); + ObjectName rootName = findRoot(runtimeBeanOns); + + Set childrenRuntimeBeans = Sets.newHashSet(runtimeBeanOns); + childrenRuntimeBeans.remove(rootName); + + instance.appendChild(instanceRuntime.toXml(rootName, childrenRuntimeBeans, document)); + + root.appendChild(instance); + } + + return root; + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/runtime/Runtime.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/runtime/Runtime.java new file mode 100644 index 0000000000..da281269e6 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/mapping/runtime/Runtime.java @@ -0,0 +1,78 @@ +/* + * 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.netconf.confignetconfconnector.mapping.runtime; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import org.opendaylight.controller.config.api.jmx.ObjectNameUtil; +import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.management.ObjectName; +import java.util.Map; +import java.util.Set; + +public class Runtime { + + private final Map> moduleRuntimes; + + public Runtime(Map> moduleRuntimes) { + this.moduleRuntimes = moduleRuntimes; + } + + private Map> mapInstancesToModules(Set instancesToMap) { + Map> retVal = Maps.newHashMap(); + + for (ObjectName objectName : instancesToMap) { + String moduleName = ObjectNameUtil.getFactoryName(objectName); + + Multimap multimap = retVal.get(moduleName); + if (multimap == null) { + multimap = HashMultimap.create(); + retVal.put(moduleName, multimap); + } + + String instanceName = ObjectNameUtil.getInstanceName(objectName); + + multimap.put(instanceName, objectName); + } + + return retVal; + } + + public Element toXml(Set instancesToMap, Document document) { + Element root = document.createElement(XmlNetconfConstants.DATA_KEY); + + Element modulesElement = document.createElement(XmlNetconfConstants.MODULES_KEY); + XmlUtil.addNamespaceAttr(modulesElement, + XmlNetconfConstants.URN_OPENDAYLIGHT_PARAMS_XML_NS_YANG_CONTROLLER_CONFIG); + root.appendChild(modulesElement); + + Map> moduleToInstances = mapInstancesToModules(instancesToMap); + + for (String localNamespace : moduleRuntimes.keySet()) { + for (String moduleName : moduleRuntimes.get(localNamespace).keySet()) { + Multimap instanceToRbe = moduleToInstances.get(moduleName); + + if (instanceToRbe == null) + continue; + + ModuleRuntime moduleRuntime = moduleRuntimes.get(localNamespace).get(moduleName); + Element innerXml = moduleRuntime.toXml(localNamespace, instanceToRbe, document); + modulesElement.appendChild(innerXml); + } + } + + return root; + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/AbstractConfigNetconfOperation.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/AbstractConfigNetconfOperation.java new file mode 100644 index 0000000000..6689759ffb --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/AbstractConfigNetconfOperation.java @@ -0,0 +1,46 @@ +/* + * 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.netconf.confignetconfconnector.operations; + +import org.opendaylight.controller.config.util.ConfigRegistryClient; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfOperationRouter; +import org.opendaylight.controller.netconf.mapping.api.HandlingPriority; +import org.opendaylight.controller.netconf.util.mapping.AbstractNetconfOperation; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public abstract class AbstractConfigNetconfOperation extends AbstractNetconfOperation { + + protected final ConfigRegistryClient configRegistryClient; + + protected AbstractConfigNetconfOperation(ConfigRegistryClient configRegistryClient, + String netconfSessionIdForReporting) { + super(netconfSessionIdForReporting); + this.configRegistryClient = configRegistryClient; + } + + @Override + protected HandlingPriority canHandle(String operationName, String operationNamespace) { + // TODO check namespace + return operationName.equals(getOperationName()) ? HandlingPriority.HANDLE_WITH_DEFAULT_PRIORITY + : HandlingPriority.CANNOT_HANDLE; + } + + protected abstract String getOperationName(); + + @Override + protected Element handle(Document document, XmlElement operationElement, NetconfOperationRouter opRouter) + throws NetconfDocumentedException { + return handle(document, operationElement); + } + + protected abstract Element handle(Document document, XmlElement operationElement) throws NetconfDocumentedException; +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/Commit.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/Commit.java new file mode 100644 index 0000000000..8820d58a98 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/Commit.java @@ -0,0 +1,74 @@ +/* + * 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.netconf.confignetconfconnector.operations; + +import java.util.HashMap; +import java.util.Map; + +import org.opendaylight.controller.config.api.jmx.CommitStatus; +import org.opendaylight.controller.config.util.ConfigRegistryClient; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorSeverity; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorTag; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorType; +import org.opendaylight.controller.netconf.confignetconfconnector.transactions.TransactionProvider; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class Commit extends AbstractConfigNetconfOperation { + + private static final Logger logger = LoggerFactory.getLogger(Commit.class); + + private final TransactionProvider transactionProvider; + + public Commit(TransactionProvider transactionProvider, ConfigRegistryClient configRegistryClient, + String netconfSessionIdForReporting) { + super(configRegistryClient, netconfSessionIdForReporting); + this.transactionProvider = transactionProvider; + } + + private static void checkXml(XmlElement xml) { + xml.checkName(XmlNetconfConstants.COMMIT); + xml.checkNamespace(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0); + } + + @Override + protected String getOperationName() { + return XmlNetconfConstants.COMMIT; + } + + @Override + protected Element handle(Document document, XmlElement xml) throws NetconfDocumentedException { + checkXml(xml); + + CommitStatus status; + try { + status = this.transactionProvider.commitTransaction(); + } catch (final IllegalStateException e) { + logger.warn("Commit failed: ", e); + final Map errorInfo = new HashMap<>(); + errorInfo.put(ErrorTag.operation_failed.name(), + "Operation failed. Use 'get-config' or 'edit-config' before triggering 'commit' operation"); + throw new NetconfDocumentedException(e.getMessage(), e, ErrorType.application, ErrorTag.operation_failed, + ErrorSeverity.error, errorInfo); + } catch (final NetconfDocumentedException e) { + throw new NetconfDocumentedException( + "Unable to retrieve config snapshot after commit for persister, details: " + e.getMessage(), + ErrorType.application, ErrorTag.operation_failed, ErrorSeverity.error, e.getErrorInfo()); + } + logger.info("Datastore {} committed successfully: {}", Datastore.candidate, status); + + return document.createElement(XmlNetconfConstants.OK); + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/Datastore.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/Datastore.java new file mode 100644 index 0000000000..d736595719 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/Datastore.java @@ -0,0 +1,36 @@ +/* + * 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.netconf.confignetconfconnector.operations; + +import org.opendaylight.controller.netconf.confignetconfconnector.operations.getconfig.CandidateDatastoreQueryStrategy; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.getconfig.DatastoreQueryStrategy; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.getconfig.RunningDatastoreQueryStrategy; +import org.opendaylight.controller.netconf.confignetconfconnector.transactions.TransactionProvider; + +public enum Datastore { + + running, candidate; + + /** + * @param source + * @param transactionProvider + * @return + */ + public static DatastoreQueryStrategy getInstanceQueryStrategy(Datastore source, + TransactionProvider transactionProvider) { + switch (source) { + case running: + return new RunningDatastoreQueryStrategy(); + case candidate: + return new CandidateDatastoreQueryStrategy(transactionProvider); + default: + throw new UnsupportedOperationException("Unimplemented datastore query strategy for " + source); + } + } +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/DiscardChanges.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/DiscardChanges.java new file mode 100644 index 0000000000..b8fa5dd86e --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/DiscardChanges.java @@ -0,0 +1,78 @@ +/* + * 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.netconf.confignetconfconnector.operations; + +import java.util.HashMap; +import java.util.Map; + +import org.opendaylight.controller.config.util.ConfigRegistryClient; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorSeverity; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorTag; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorType; +import org.opendaylight.controller.netconf.confignetconfconnector.transactions.TransactionProvider; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class DiscardChanges extends AbstractConfigNetconfOperation { + + public static final String DISCARD = "discard-changes"; + + private static final Logger logger = LoggerFactory.getLogger(DiscardChanges.class); + + private final TransactionProvider transactionProvider; + + public DiscardChanges(final TransactionProvider transactionProvider, ConfigRegistryClient configRegistryClient, + String netconfSessionIdForReporting) { + super(configRegistryClient, netconfSessionIdForReporting); + this.transactionProvider = transactionProvider; + } + + private static void fromXml(XmlElement xml) { + xml.checkName(DISCARD); + xml.checkNamespace(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0); + } + + @Override + protected String getOperationName() { + return DISCARD; + } + + @Override + protected Element handle(Document document, XmlElement xml) throws NetconfDocumentedException { + try { + fromXml(xml); + } catch (final IllegalArgumentException e) { + logger.warn("Rpc error: {}", ErrorTag.bad_attribute, e); + final Map errorInfo = new HashMap<>(); + errorInfo.put(ErrorTag.bad_attribute.name(), e.getMessage()); + throw new NetconfDocumentedException(e.getMessage(), e, ErrorType.rpc, ErrorTag.bad_attribute, + ErrorSeverity.error, errorInfo); + } + + try { + this.transactionProvider.abortTransaction(); + } catch (final IllegalStateException e) { + logger.warn("Abort failed: ", e); + final Map errorInfo = new HashMap<>(); + errorInfo + .put(ErrorTag.operation_failed.name(), + "Operation failed. Use 'get-config' or 'edit-config' before triggering 'discard-changes' operation"); + throw new NetconfDocumentedException(e.getMessage(), e, ErrorType.application, ErrorTag.operation_failed, + ErrorSeverity.error, errorInfo); + } + logger.info("Changes discarded successfully from datastore {}", Datastore.candidate); + + return document.createElement(XmlNetconfConstants.OK); + } +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/Validate.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/Validate.java new file mode 100644 index 0000000000..b8cae43643 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/Validate.java @@ -0,0 +1,105 @@ +/* + * 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.netconf.confignetconfconnector.operations; + +import com.google.common.base.Preconditions; +import org.opendaylight.controller.config.api.ValidationException; +import org.opendaylight.controller.config.util.ConfigRegistryClient; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorSeverity; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorTag; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorType; +import org.opendaylight.controller.netconf.confignetconfconnector.transactions.TransactionProvider; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import java.util.HashMap; +import java.util.Map; + +public class Validate extends AbstractConfigNetconfOperation { + + public static final String VALIDATE = "validate"; + + private static final Logger logger = LoggerFactory.getLogger(Validate.class); + + private final TransactionProvider transactionProvider; + + public Validate(final TransactionProvider transactionProvider, ConfigRegistryClient configRegistryClient, + String netconfSessionIdForReporting) { + super(configRegistryClient, netconfSessionIdForReporting); + this.transactionProvider = transactionProvider; + } + + private void checkXml(XmlElement xml) { + xml.checkName(VALIDATE); + xml.checkNamespace(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0); + + XmlElement sourceElement = xml.getOnlyChildElement(XmlNetconfConstants.SOURCE_KEY, + XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0); + XmlElement sourceChildNode = sourceElement.getOnlyChildElement(); + + sourceChildNode.checkNamespace(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0); + String datastoreValue = sourceChildNode.getName(); + Datastore sourceDatastore = Datastore.valueOf(datastoreValue); + + Preconditions.checkState(sourceDatastore == Datastore.candidate, "Only " + Datastore.candidate + + " is supported as source for " + VALIDATE + " but was " + datastoreValue); + } + + @Override + protected String getOperationName() { + return VALIDATE; + } + + @Override + protected Element handle(Document document, XmlElement xml) throws NetconfDocumentedException { + try { + checkXml(xml); + } catch (IllegalStateException e) { + logger.warn("Rpc error: {}", ErrorTag.missing_attribute, e); + final Map errorInfo = new HashMap<>(); + errorInfo.put(ErrorTag.missing_attribute.name(), "Missing value of datastore attribute"); + throw new NetconfDocumentedException(e.getMessage(), e, ErrorType.rpc, ErrorTag.missing_attribute, + ErrorSeverity.error, errorInfo); + } catch (final IllegalArgumentException e) { + logger.warn("Rpc error: {}", ErrorTag.bad_attribute, e); + final Map errorInfo = new HashMap<>(); + errorInfo.put(ErrorTag.bad_attribute.name(), e.getMessage()); + throw new NetconfDocumentedException(e.getMessage(), e, ErrorType.rpc, ErrorTag.bad_attribute, + ErrorSeverity.error, errorInfo); + } + + try { + transactionProvider.validateTransaction(); + } catch (ValidationException e) { + logger.warn("Validation failed", e); + final Map errorInfo = new HashMap<>(); + errorInfo.put(ErrorTag.operation_failed.name(), "Validation failed"); + throw new NetconfDocumentedException(e.getMessage(), e, ErrorType.application, ErrorTag.operation_failed, + ErrorSeverity.error, errorInfo); + } catch (IllegalStateException e) { + logger.warn("Validation failed", e); + final Map errorInfo = new HashMap<>(); + errorInfo + .put(ErrorTag.operation_failed.name(), + "Datastore is not present. Use 'get-config' or 'edit-config' before triggering 'operations' operation"); + throw new NetconfDocumentedException(e.getMessage(), e, ErrorType.application, ErrorTag.operation_failed, + ErrorSeverity.error, errorInfo); + + } + + logger.info("Datastore {} validated successfully", Datastore.candidate); + + return document.createElement(XmlNetconfConstants.OK); + } +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/AbstractEditConfigStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/AbstractEditConfigStrategy.java new file mode 100644 index 0000000000..d8ea7d7af7 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/AbstractEditConfigStrategy.java @@ -0,0 +1,45 @@ +/* + * 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.netconf.confignetconfconnector.operations.editconfig; + +import java.util.Map; + +import javax.management.InstanceNotFoundException; +import javax.management.ObjectName; + +import org.opendaylight.controller.config.util.ConfigTransactionClient; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.fromxml.AttributeConfigElement; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class AbstractEditConfigStrategy implements EditConfigStrategy { + + private static final Logger logger = LoggerFactory.getLogger(AbstractEditConfigStrategy.class); + + @Override + public void executeConfiguration(String module, String instance, Map configuration, + ConfigTransactionClient ta) { + + try { + ObjectName on = ta.lookupConfigBean(module, instance); + logger.debug("ServiceInstance for {} {} located successfully under {}", module, instance, on); + executeStrategy(configuration, ta, on); + } catch (InstanceNotFoundException e) { + handleMissingInstance(configuration, ta, module, instance); + } + + } + + abstract void handleMissingInstance(Map configuration, ConfigTransactionClient ta, + String module, String instance); + + abstract void executeStrategy(Map configuration, ConfigTransactionClient ta, + ObjectName objectName); + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/DeleteEditConfigStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/DeleteEditConfigStrategy.java new file mode 100644 index 0000000000..ffe107f8ce --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/DeleteEditConfigStrategy.java @@ -0,0 +1,40 @@ +/* + * 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.netconf.confignetconfconnector.operations.editconfig; + +import java.util.Map; + +import javax.management.InstanceNotFoundException; +import javax.management.ObjectName; + +import org.opendaylight.controller.config.util.ConfigTransactionClient; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.fromxml.AttributeConfigElement; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DeleteEditConfigStrategy extends AbstractEditConfigStrategy { + + private static final Logger logger = LoggerFactory.getLogger(DeleteEditConfigStrategy.class); + + @Override + void handleMissingInstance(Map configuration, ConfigTransactionClient ta, + String module, String instance) { + throw new IllegalStateException("Unable to delete " + module + ":" + instance + " , ServiceInstance not found"); + } + + @Override + void executeStrategy(Map configuration, ConfigTransactionClient ta, ObjectName on) { + try { + ta.destroyModule(on); + logger.debug("ServiceInstance {} deleted successfully", on); + } catch (InstanceNotFoundException e) { + throw new IllegalStateException("Unable to delete " + on, e); + } + } +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/EditConfig.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/EditConfig.java new file mode 100644 index 0000000000..de21f310e2 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/EditConfig.java @@ -0,0 +1,206 @@ +/* + * 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.netconf.confignetconfconnector.operations.editconfig; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import org.opendaylight.controller.config.api.JmxAttributeValidationException; +import org.opendaylight.controller.config.api.ValidationException; +import org.opendaylight.controller.config.util.ConfigRegistryClient; +import org.opendaylight.controller.config.util.ConfigTransactionClient; +import org.opendaylight.controller.config.yang.store.api.YangStoreSnapshot; +import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorSeverity; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorTag; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorType; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config.*; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.AbstractConfigNetconfOperation; +import org.opendaylight.controller.netconf.confignetconfconnector.transactions.TransactionProvider; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.management.ObjectName; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +public class EditConfig extends AbstractConfigNetconfOperation { + + private static final Logger logger = LoggerFactory.getLogger(EditConfig.class); + + private final YangStoreSnapshot yangStoreSnapshot; + + private final TransactionProvider transactionProvider; + private EditConfigXmlParser editConfigXmlParser; + + public EditConfig(YangStoreSnapshot yangStoreSnapshot, TransactionProvider transactionProvider, + ConfigRegistryClient configRegistryClient, String netconfSessionIdForReporting) { + super(configRegistryClient, netconfSessionIdForReporting); + this.yangStoreSnapshot = yangStoreSnapshot; + this.transactionProvider = transactionProvider; + this.editConfigXmlParser = new EditConfigXmlParser(); + } + + @VisibleForTesting + Element getResponseInternal(final Document document, + final EditConfigXmlParser.EditConfigExecution editConfigExecution) throws NetconfDocumentedException { + if (editConfigExecution.shouldTest()) { + executeTests(configRegistryClient, editConfigExecution); + } + + if (editConfigExecution.shouldSet()) { + executeSet(configRegistryClient, editConfigExecution); + } + + logger.info("Operation {} successful", EditConfigXmlParser.EDIT_CONFIG); + + return document.createElement(XmlNetconfConstants.OK); + } + + private void executeSet(ConfigRegistryClient configRegistryClient, + EditConfigXmlParser.EditConfigExecution editConfigExecution) throws NetconfDocumentedException { + try { + set(configRegistryClient, editConfigExecution); + } catch (IllegalStateException | JmxAttributeValidationException | ValidationException e) { + logger.warn("Set phase for {} failed", EditConfigXmlParser.EDIT_CONFIG, e); + final Map errorInfo = new HashMap<>(); + errorInfo.put(ErrorTag.operation_failed.name(), e.getMessage()); + throw new NetconfDocumentedException("Test phase: " + e.getMessage(), e, ErrorType.application, + ErrorTag.operation_failed, ErrorSeverity.error, errorInfo); + } + logger.debug("Set phase for {} operation successful", EditConfigXmlParser.EDIT_CONFIG); + } + + private void executeTests(ConfigRegistryClient configRegistryClient, + EditConfigXmlParser.EditConfigExecution editConfigExecution) throws NetconfDocumentedException { + try { + test(configRegistryClient, editConfigExecution.resolvedXmlElements); + } catch (IllegalStateException | JmxAttributeValidationException | ValidationException e) { + logger.warn("Test phase for {} failed", EditConfigXmlParser.EDIT_CONFIG, e); + final Map errorInfo = new HashMap<>(); + errorInfo.put(ErrorTag.operation_failed.name(), e.getMessage()); + throw new NetconfDocumentedException("Test phase: " + e.getMessage(), e, ErrorType.application, + ErrorTag.operation_failed, ErrorSeverity.error, errorInfo); + } + logger.debug("Test phase for {} operation successful", EditConfigXmlParser.EDIT_CONFIG); + } + + private void test(ConfigRegistryClient configRegistryClient, + Map> resolvedModules) { + ObjectName taON = transactionProvider.getTestTransaction(); + try { + + // default strategy = replace wipes config + if (EditStrategyType.defaultStrategy() == EditStrategyType.replace) { + transactionProvider.wipeTestTransaction(taON); + } + setOnTransaction(configRegistryClient, resolvedModules, taON); + transactionProvider.validateTestTransaction(taON); + } finally { + transactionProvider.abortTestTransaction(taON); + } + } + + private void set(ConfigRegistryClient configRegistryClient, + EditConfigXmlParser.EditConfigExecution editConfigExecution) { + ObjectName taON = transactionProvider.getOrCreateTransaction(); + + // default strategy = replace wipes config + if (EditStrategyType.defaultStrategy() == EditStrategyType.replace) { + transactionProvider.wipeTransaction(); + } + setOnTransaction(configRegistryClient, editConfigExecution.resolvedXmlElements, taON); + } + + private void setOnTransaction(ConfigRegistryClient configRegistryClient, + Map> resolvedXmlElements, ObjectName taON) { + ConfigTransactionClient ta = configRegistryClient.getConfigTransactionClient(taON); + + for (Multimap modulesToResolved : resolvedXmlElements.values()) { + for (Entry moduleToResolved : modulesToResolved.entries()) { + String moduleName = moduleToResolved.getKey(); + + ModuleElementResolved moduleElementResolved = moduleToResolved.getValue(); + String instanceName = moduleElementResolved.getInstanceName(); + + InstanceConfigElementResolved ice = moduleElementResolved.getInstanceConfigElementResolved(); + EditConfigStrategy strategy = ice.getEditStrategy(); + strategy.executeConfiguration(moduleName, instanceName, ice.getConfiguration(), ta); + } + } + } + + public static Config getConfigMapping(ConfigRegistryClient configRegistryClient, + Map> mBeanEntries) { + Map> factories = transform(configRegistryClient, mBeanEntries); + return new Config(factories); + } + + // TODO refactor + private static Map> transform(final ConfigRegistryClient configRegistryClient, + Map> mBeanEntries) { + return Maps.transformEntries(mBeanEntries, + new Maps.EntryTransformer, Map>() { + + @Override + public Map transformEntry(String arg0, Map arg1) { + return Maps.transformEntries(arg1, + new Maps.EntryTransformer() { + + @Override + public ModuleConfig transformEntry(String key, ModuleMXBeanEntry value) { + return new ModuleConfig(key, new InstanceConfig(configRegistryClient, value + .getAttributes())); + } + }); + } + }); + } + + @Override + protected String getOperationName() { + return EditConfigXmlParser.EDIT_CONFIG; + } + + @Override + protected Element handle(Document document, XmlElement xml) throws NetconfDocumentedException { + + EditConfigXmlParser.EditConfigExecution editConfigExecution; + Config cfg = getConfigMapping(configRegistryClient, yangStoreSnapshot.getModuleMXBeanEntryMap()); + try { + editConfigExecution = editConfigXmlParser.fromXml(xml, cfg); + } catch (IllegalStateException e) { + logger.warn("Error parsing xml", e); + final Map errorInfo = new HashMap<>(); + errorInfo.put(ErrorTag.missing_attribute.name(), "Missing value for 'target' attribute"); + throw new NetconfDocumentedException(e.getMessage(), ErrorType.rpc, ErrorTag.missing_attribute, + ErrorSeverity.error, errorInfo); + } catch (final IllegalArgumentException e) { + logger.warn("Error parsing xml", e); + final Map errorInfo = new HashMap<>(); + errorInfo.put(ErrorTag.bad_attribute.name(), e.getMessage()); + throw new NetconfDocumentedException(e.getMessage(), ErrorType.rpc, ErrorTag.bad_attribute, + ErrorSeverity.error, errorInfo); + } catch (final UnsupportedOperationException e) { + logger.warn("Unsupported", e); + final Map errorInfo = new HashMap<>(); + errorInfo.put(ErrorTag.operation_not_supported.name(), "Unsupported option for 'edit-config'"); + throw new NetconfDocumentedException(e.getMessage(), ErrorType.application, + ErrorTag.operation_not_supported, ErrorSeverity.error, errorInfo); + } + + return getResponseInternal(document, editConfigExecution); + } +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/EditConfigStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/EditConfigStrategy.java new file mode 100644 index 0000000000..6b7b622d56 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/EditConfigStrategy.java @@ -0,0 +1,21 @@ +/* + * 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.netconf.confignetconfconnector.operations.editconfig; + +import org.opendaylight.controller.config.util.ConfigTransactionClient; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.fromxml.AttributeConfigElement; + +import java.util.Map; + +public interface EditConfigStrategy { + + void executeConfiguration(String module, String instance, Map configuration, + ConfigTransactionClient ta); + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/EditConfigXmlParser.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/EditConfigXmlParser.java new file mode 100644 index 0000000000..d835dfd30f --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/EditConfigXmlParser.java @@ -0,0 +1,140 @@ +/* + * 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.netconf.confignetconfconnector.operations.editconfig; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.Multimap; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config.Config; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config.ModuleElementResolved; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.Datastore; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; +import java.util.Map; + +public class EditConfigXmlParser { + + private static final Logger logger = LoggerFactory.getLogger(EditConfigXmlParser.class); + + public static final String EDIT_CONFIG = "edit-config"; + public static final String DEFAULT_OPERATION_KEY = "default-operation"; + static final String ERROR_OPTION_KEY = "error-option"; + static final String DEFAULT_ERROR_OPTION = "stop-on-error"; + static final String TARGET_KEY = "target"; + static final String TEST_OPTION_KEY = "test-option"; + + public EditConfigXmlParser() { + } + + EditConfigXmlParser.EditConfigExecution fromXml(final XmlElement xml, final Config cfgMapping) + throws NetconfDocumentedException { + + EditStrategyType.resetDefaultStrategy(); + + xml.checkName(EditConfigXmlParser.EDIT_CONFIG); + xml.checkNamespace(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0); + + XmlElement targetElement = xml.getOnlyChildElementWithSameNamespace(EditConfigXmlParser.TARGET_KEY); + XmlElement targetChildNode = targetElement.getOnlyChildElementWithSameNamespace(); + String datastoreValue = targetChildNode.getName(); + Datastore targetDatastore = Datastore.valueOf(datastoreValue); + logger.debug("Setting {} to '{}'", EditConfigXmlParser.TARGET_KEY, targetDatastore); + + // check target + Preconditions.checkArgument(targetDatastore == Datastore.candidate, + "Only %s datastore supported for edit config but was: %s", Datastore.candidate, targetDatastore); + + // Test option + TestOption testOption; + Optional testOptionElementOpt = xml + .getOnlyChildElementWithSameNamespaceOptionally(EditConfigXmlParser.TEST_OPTION_KEY); + if (testOptionElementOpt.isPresent()) { + String testOptionValue = testOptionElementOpt.get().getTextContent(); + testOption = EditConfigXmlParser.TestOption.getFromXmlName(testOptionValue); + } else { + testOption = EditConfigXmlParser.TestOption.getDefault(); + } + logger.debug("Setting {} to '{}'", EditConfigXmlParser.TEST_OPTION_KEY, testOption); + + // Error option + Optional errorOptionElement = xml + .getOnlyChildElementWithSameNamespaceOptionally(EditConfigXmlParser.ERROR_OPTION_KEY); + if (errorOptionElement.isPresent()) { + String errorOptionParsed = errorOptionElement.get().getTextContent(); + if (false == errorOptionParsed.equals(EditConfigXmlParser.DEFAULT_ERROR_OPTION)) + throw new UnsupportedOperationException("Only " + EditConfigXmlParser.DEFAULT_ERROR_OPTION + + " supported for " + EditConfigXmlParser.ERROR_OPTION_KEY + ", was " + errorOptionParsed); + } + + // Default op + Optional defaultContent = xml + .getOnlyChildElementWithSameNamespaceOptionally(EditConfigXmlParser.DEFAULT_OPERATION_KEY); + if (defaultContent.isPresent()) + EditStrategyType.setDefaultStrategy(EditStrategyType.valueOf(defaultContent.get().getTextContent())); + + XmlElement configElement = xml.getOnlyChildElementWithSameNamespace(XmlNetconfConstants.CONFIG_KEY); + + return new EditConfigXmlParser.EditConfigExecution(xml, cfgMapping, configElement, testOption); + } + + private void removeMountpointsFromConfig(XmlElement configElement, XmlElement mountpointsElement) { + configElement.getDomElement().removeChild(mountpointsElement.getDomElement()); + } + + @VisibleForTesting + static enum TestOption { + testOnly, set, testThenSet; + + static TestOption getFromXmlName(String testOptionXmlName) { + switch (testOptionXmlName) { + case "test-only": + return testOnly; + case "test-then-set": + return testThenSet; + case "set": + return set; + default: + throw new IllegalArgumentException("Unsupported test option " + testOptionXmlName + " supported: " + + Arrays.toString(TestOption.values())); + } + } + + public static TestOption getDefault() { + return testThenSet; + } + + } + + @VisibleForTesting + static class EditConfigExecution { + XmlElement editConfigXml; + Map> resolvedXmlElements; + TestOption testOption; + + EditConfigExecution(XmlElement xml, Config configResolver, XmlElement configElement, TestOption testOption) { + this.editConfigXml = xml; + this.resolvedXmlElements = configResolver.fromXml(configElement); + this.testOption = testOption; + } + + boolean shouldTest() { + return testOption == TestOption.testOnly || testOption == TestOption.testThenSet; + } + + boolean shouldSet() { + return testOption == TestOption.set || testOption == TestOption.testThenSet; + } + } +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/EditStrategyType.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/EditStrategyType.java new file mode 100644 index 0000000000..a7a0518cc5 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/EditStrategyType.java @@ -0,0 +1,82 @@ +/* + * 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.netconf.confignetconfconnector.operations.editconfig; + +import java.util.EnumSet; +import java.util.Set; + +import com.google.common.base.Preconditions; + +public enum EditStrategyType { + // can be default + merge, replace, none, + // additional per element + delete, remove; + + private static final Set defaultStrats = EnumSet.of(merge, replace, none); + + private static EditStrategyType defaultStrat = merge; + + public static EditStrategyType defaultStrategy() { + return defaultStrat; + } + + public static void setDefaultStrategy(EditStrategyType strat) { + Preconditions.checkArgument(strat.canBeDefault(), "Default edit strategy can be only of value " + defaultStrats + + ", but was " + strat); + defaultStrat = strat; + } + + public static void resetDefaultStrategy() { + setDefaultStrategy(EditStrategyType.merge); + } + + public boolean isEnforcing() { + switch (this) { + case merge: + case none: + case remove: + case delete: + return false; + case replace: + return true; + + default: + throw new IllegalStateException("Default edit strategy can be only of value " + defaultStrats + " but was " + + this); + } + } + + private static final EnumSet defaults; + + static { + defaults = EnumSet.of(merge, replace, none); + } + + private boolean canBeDefault() { + return defaults.contains(this); + } + + public EditConfigStrategy getFittingStrategy() { + switch (this) { + case merge: + return new MergeEditConfigStrategy(); + case replace: + return new ReplaceEditConfigStrategy(); + case delete: + return new DeleteEditConfigStrategy(); + case remove: + return new RemoveEditConfigStrategy(); + case none: + return new NoneEditConfigStrategy(); + default: + throw new UnsupportedOperationException("Unimplemented edit config strategy" + this); + } + } +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/MergeEditConfigStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/MergeEditConfigStrategy.java new file mode 100644 index 0000000000..2a4a784a8a --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/MergeEditConfigStrategy.java @@ -0,0 +1,60 @@ +/* + * 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.netconf.confignetconfconnector.operations.editconfig; + +import java.util.Map; +import java.util.Map.Entry; + +import javax.management.Attribute; +import javax.management.InstanceAlreadyExistsException; +import javax.management.ObjectName; + +import org.opendaylight.controller.config.util.ConfigTransactionClient; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.fromxml.AttributeConfigElement; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MergeEditConfigStrategy extends AbstractEditConfigStrategy { + + private static final Logger logger = LoggerFactory.getLogger(MergeEditConfigStrategy.class); + + @Override + void handleMissingInstance(Map configuration, ConfigTransactionClient ta, + String module, String instance) { + ObjectName on; + try { + on = ta.createModule(module, instance); + logger.info("New instance for {} {} created under name {}", module, instance, on); + executeStrategy(configuration, ta, on); + } catch (InstanceAlreadyExistsException e1) { + throw new IllegalStateException("Unable to create instance for " + module + " : " + instance); + } + } + + @Override + void executeStrategy(Map configuration, ConfigTransactionClient ta, ObjectName on) { + for (Entry configAttributeEntry : configuration.entrySet()) { + try { + AttributeConfigElement ace = configAttributeEntry.getValue(); + + if (!ace.getResolvedValue().isPresent()) { + logger.debug("Skipping attribute {} for {}", configAttributeEntry.getKey(), on); + continue; + } + + Object value = ace.getResolvedValue().get(); + ta.setAttribute(on, ace.getJmxName(), new Attribute(ace.getJmxName(), value)); + logger.debug("Attribute {} set to {} for {}", configAttributeEntry.getKey(), value, on); + } catch (Exception e) { + throw new IllegalStateException("Unable to set attributes for " + on + ", Error with attribute " + + configAttributeEntry.getKey() + ":" + configAttributeEntry.getValue(), e); + } + } + } +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/NoneEditConfigStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/NoneEditConfigStrategy.java new file mode 100644 index 0000000000..db11ce381e --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/NoneEditConfigStrategy.java @@ -0,0 +1,28 @@ +/* + * 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.netconf.confignetconfconnector.operations.editconfig; + +import java.util.Map; + +import org.opendaylight.controller.config.util.ConfigTransactionClient; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.fromxml.AttributeConfigElement; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class NoneEditConfigStrategy implements EditConfigStrategy { + + private static final Logger logger = LoggerFactory.getLogger(NoneEditConfigStrategy.class); + + @Override + public void executeConfiguration(String module, String instance, Map configuration, + ConfigTransactionClient ta) { + logger.debug("Skipping configuration element for {}:{}", module, instance); + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/RemoveEditConfigStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/RemoveEditConfigStrategy.java new file mode 100644 index 0000000000..76ca09433a --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/RemoveEditConfigStrategy.java @@ -0,0 +1,27 @@ +/* + * 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.netconf.confignetconfconnector.operations.editconfig; + +import org.opendaylight.controller.config.util.ConfigTransactionClient; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.fromxml.AttributeConfigElement; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; + +public class RemoveEditConfigStrategy extends DeleteEditConfigStrategy { + + private static final Logger logger = LoggerFactory.getLogger(RemoveEditConfigStrategy.class); + + @Override + void handleMissingInstance(Map configuration, ConfigTransactionClient ta, + String module, String instance) { + logger.warn("Unable to delete {}:{}, ServiceInstance not found", module, instance); + } +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/ReplaceEditConfigStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/ReplaceEditConfigStrategy.java new file mode 100644 index 0000000000..0091d6cc84 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/ReplaceEditConfigStrategy.java @@ -0,0 +1,63 @@ +/* + * 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.netconf.confignetconfconnector.operations.editconfig; + +import java.util.Map; +import java.util.Map.Entry; + +import javax.management.Attribute; +import javax.management.InstanceAlreadyExistsException; +import javax.management.ObjectName; + +import org.opendaylight.controller.config.util.ConfigTransactionClient; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.fromxml.AttributeConfigElement; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ReplaceEditConfigStrategy extends AbstractEditConfigStrategy { + + private static final Logger logger = LoggerFactory.getLogger(ReplaceEditConfigStrategy.class); + + @Override + void handleMissingInstance(Map configuration, ConfigTransactionClient ta, + String module, String instance) { + try { + ObjectName on = ta.createModule(module, instance); + logger.debug("New instance for {} {} created under name {}", module, instance, on); + executeStrategy(configuration, ta, on); + } catch (InstanceAlreadyExistsException e) { + logger.warn("Error creating instance {}:{}, replace failed", module, instance, e); + throw new IllegalStateException("Unable to create new instance for " + module + " : " + instance, e); + } + } + + @Override + void executeStrategy(Map configuration, ConfigTransactionClient ta, ObjectName on) { + for (Entry configAttributeEntry : configuration.entrySet()) { + try { + AttributeConfigElement ace = configAttributeEntry.getValue(); + + if (!ace.getResolvedValue().isPresent()) { + Object value = ace.getResolvedDefaultValue(); + ta.setAttribute(on, ace.getJmxName(), new Attribute(ace.getJmxName(), value)); + logger.debug("Attribute {} set to default value {} for {}", configAttributeEntry.getKey(), value, + on); + } else { + Object value = ace.getResolvedValue().get(); + ta.setAttribute(on, ace.getJmxName(), new Attribute(ace.getJmxName(), value)); + logger.debug("Attribute {} set to value {} for {}", configAttributeEntry.getKey(), value, on); + } + + } catch (Exception e) { + throw new IllegalStateException("Unable to set attributes for " + on + ", Error with attribute " + + configAttributeEntry.getKey() + ":" + configAttributeEntry.getValue(), e); + } + } + } +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/get/Get.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/get/Get.java new file mode 100644 index 0000000000..b93843d28e --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/get/Get.java @@ -0,0 +1,143 @@ +/* + * 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.netconf.confignetconfconnector.operations.get; + +import com.google.common.collect.Maps; +import org.opendaylight.controller.config.util.ConfigRegistryClient; +import org.opendaylight.controller.config.yang.store.api.YangStoreSnapshot; +import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry; +import org.opendaylight.controller.config.yangjmxgenerator.RuntimeBeanEntry; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorSeverity; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorTag; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorType; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config.InstanceConfig; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.runtime.InstanceRuntime; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.runtime.ModuleRuntime; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.runtime.Runtime; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.AbstractConfigNetconfOperation; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.management.ObjectName; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class Get extends AbstractConfigNetconfOperation { + + public static final String GET = "get"; + + private final YangStoreSnapshot yangStoreSnapshot; + private static final Logger logger = LoggerFactory.getLogger(Get.class); + + public Get(YangStoreSnapshot yangStoreSnapshot, ConfigRegistryClient configRegistryClient, + String netconfSessionIdForReporting) { + super(configRegistryClient, netconfSessionIdForReporting); + this.yangStoreSnapshot = yangStoreSnapshot; + } + + private Map> createModuleRuntimes(ConfigRegistryClient configRegistryClient, + Map> mBeanEntries) { + Map> retVal = Maps.newHashMap(); + + for (String namespace : mBeanEntries.keySet()) { + + Map innerMap = Maps.newHashMap(); + Map entriesFromNamespace = mBeanEntries.get(namespace); + for (String module : entriesFromNamespace.keySet()) { + + ModuleMXBeanEntry mbe = entriesFromNamespace.get(module); + + Map cache = Maps.newHashMap(); + RuntimeBeanEntry root = null; + for (RuntimeBeanEntry rbe : mbe.getRuntimeBeans()) { + cache.put(rbe, new InstanceConfig(configRegistryClient, rbe.getYangPropertiesToTypesMap())); + if (rbe.isRoot()) + root = rbe; + } + + if (root == null) + continue; + + InstanceRuntime rootInstanceRuntime = createInstanceRuntime(root, cache); + ModuleRuntime moduleRuntime = new ModuleRuntime(module, rootInstanceRuntime); + innerMap.put(module, moduleRuntime); + } + + retVal.put(namespace, innerMap); + } + return retVal; + } + + private InstanceRuntime createInstanceRuntime(RuntimeBeanEntry root, Map cache) { + Map children = Maps.newHashMap(); + for (RuntimeBeanEntry child : root.getChildren()) { + children.put(child.getJavaNamePrefix(), createInstanceRuntime(child, cache)); + } + + return new InstanceRuntime(cache.get(root), children, createJmxToYangMap(root.getChildren())); + } + + private Map createJmxToYangMap(List children) { + Map jmxToYangNamesForChildRbe = Maps.newHashMap(); + for (RuntimeBeanEntry rbe : children) { + jmxToYangNamesForChildRbe.put(rbe.getJavaNamePrefix(), rbe.getYangName()); + } + return jmxToYangNamesForChildRbe; + } + + private static void checkXml(XmlElement xml) { + xml.checkName(GET); + xml.checkNamespace(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0); + + // Filter option - unsupported + if (xml.getChildElements(XmlNetconfConstants.FILTER).size() != 0) + throw new UnsupportedOperationException("Unsupported option " + XmlNetconfConstants.FILTER + " for " + GET); + } + + @Override + protected String getOperationName() { + return GET; + } + + @Override + protected Element handle(Document document, XmlElement xml) throws NetconfDocumentedException { + try { + checkXml(xml); + } catch (final IllegalArgumentException e) { + logger.warn("Error parsing xml", e); + final Map errorInfo = new HashMap<>(); + errorInfo.put(ErrorTag.bad_attribute.name(), e.getMessage()); + throw new NetconfDocumentedException(e.getMessage(), e, ErrorType.rpc, ErrorTag.bad_attribute, + ErrorSeverity.error, errorInfo); + } catch (final UnsupportedOperationException e) { + logger.warn("Unsupported", e); + final Map errorInfo = new HashMap<>(); + errorInfo.put(ErrorTag.operation_not_supported.name(), "Unsupported option for 'get'"); + throw new NetconfDocumentedException(e.getMessage(), e, ErrorType.application, + ErrorTag.operation_not_supported, ErrorSeverity.error, errorInfo); + } + final Set runtimeBeans = configRegistryClient.lookupRuntimeBeans(); + final Map> moduleMappings = createModuleRuntimes(configRegistryClient, + yangStoreSnapshot.getModuleMXBeanEntryMap()); + final Runtime runtime = new Runtime(moduleMappings); + + final Element element = runtime.toXml(runtimeBeans, document); + + logger.info("{} operation successful", GET); + + return element; + } +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/getconfig/CandidateDatastoreQueryStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/getconfig/CandidateDatastoreQueryStrategy.java new file mode 100644 index 0000000000..8e0a9d7cde --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/getconfig/CandidateDatastoreQueryStrategy.java @@ -0,0 +1,34 @@ +/* + * 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.netconf.confignetconfconnector.operations.getconfig; + +import java.util.Set; + +import javax.management.ObjectName; + +import org.opendaylight.controller.config.util.ConfigRegistryClient; +import org.opendaylight.controller.config.util.ConfigTransactionClient; +import org.opendaylight.controller.netconf.confignetconfconnector.transactions.TransactionProvider; + +public class CandidateDatastoreQueryStrategy implements DatastoreQueryStrategy { + + private final TransactionProvider transactionProvider; + + public CandidateDatastoreQueryStrategy(TransactionProvider transactionProvider) { + this.transactionProvider = transactionProvider; + } + + @Override + public Set queryInstances(ConfigRegistryClient configRegistryClient) { + ObjectName on = transactionProvider.getOrCreateTransaction(); + ConfigTransactionClient proxy = configRegistryClient.getConfigTransactionClient(on); + return proxy.lookupConfigBeans(); + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/getconfig/DatastoreQueryStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/getconfig/DatastoreQueryStrategy.java new file mode 100644 index 0000000000..4ae56703f8 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/getconfig/DatastoreQueryStrategy.java @@ -0,0 +1,25 @@ +/* + * 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.netconf.confignetconfconnector.operations.getconfig; + +import java.util.Set; + +import javax.management.ObjectName; + +import org.opendaylight.controller.config.util.ConfigRegistryClient; + +public interface DatastoreQueryStrategy { + + /** + * @param configRegistryClient + * @return + */ + Set queryInstances(ConfigRegistryClient configRegistryClient); + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/getconfig/GetConfig.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/getconfig/GetConfig.java new file mode 100644 index 0000000000..9b8c1503c7 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/getconfig/GetConfig.java @@ -0,0 +1,144 @@ +/* + * 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.netconf.confignetconfconnector.operations.getconfig; + +import com.google.common.base.Optional; +import com.google.common.collect.Maps; +import org.opendaylight.controller.config.util.ConfigRegistryClient; +import org.opendaylight.controller.config.yang.store.api.YangStoreSnapshot; +import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorSeverity; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorTag; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorType; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config.Config; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config.InstanceConfig; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config.ModuleConfig; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.AbstractConfigNetconfOperation; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.Datastore; +import org.opendaylight.controller.netconf.confignetconfconnector.transactions.TransactionProvider; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.management.ObjectName; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class GetConfig extends AbstractConfigNetconfOperation { + + public static final String GET_CONFIG = "get-config"; + + private final YangStoreSnapshot yangStoreSnapshot; + private final Optional maybeNamespace; + + private final TransactionProvider transactionProvider; + + private static final Logger logger = LoggerFactory.getLogger(GetConfig.class); + + public GetConfig(YangStoreSnapshot yangStoreSnapshot, Optional maybeNamespace, + TransactionProvider transactionProvider, ConfigRegistryClient configRegistryClient, + String netconfSessionIdForReporting) { + super(configRegistryClient, netconfSessionIdForReporting); + this.yangStoreSnapshot = yangStoreSnapshot; + this.maybeNamespace = maybeNamespace; + this.transactionProvider = transactionProvider; + } + + public static Datastore fromXml(XmlElement xml) { + xml.checkName(GET_CONFIG); + xml.checkNamespace(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0); + + XmlElement sourceElement = xml.getOnlyChildElement(XmlNetconfConstants.SOURCE_KEY, + XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0); + XmlElement sourceNode = sourceElement.getOnlyChildElement(); + String sourceParsed = sourceNode.getName(); + logger.debug("Setting source datastore to '{}'", sourceParsed); + Datastore sourceDatastore = Datastore.valueOf(sourceParsed); + + // Filter option - unsupported + if (xml.getChildElements(XmlNetconfConstants.FILTER).size() != 0) + throw new UnsupportedOperationException("Unsupported option " + XmlNetconfConstants.FILTER + " for " + + GET_CONFIG); + + return sourceDatastore; + + } + + private Element getResponseInternal(final Document document, final ConfigRegistryClient configRegistryClient, + final Datastore source) throws NetconfDocumentedException { + Element dataElement = document.createElement(XmlNetconfConstants.DATA_KEY); + final Set instances = Datastore.getInstanceQueryStrategy(source, this.transactionProvider) + .queryInstances(configRegistryClient); + final Config configMapping = new Config(transform(configRegistryClient, + yangStoreSnapshot.getModuleMXBeanEntryMap())); + dataElement = configMapping.toXml(instances, this.maybeNamespace, document, dataElement); + + logger.info("{} operation successful", GET_CONFIG); + + return dataElement; + } + + // TODO refactor ... duplicate code + private Map> transform(final ConfigRegistryClient configRegistryClient, + Map> mBeanEntries) { + return Maps.transformEntries(mBeanEntries, + new Maps.EntryTransformer, Map>() { + + @Override + public Map transformEntry(String arg0, Map arg1) { + return Maps.transformEntries(arg1, + new Maps.EntryTransformer() { + + @Override + public ModuleConfig transformEntry(String key, ModuleMXBeanEntry value) { + return new ModuleConfig(key, new InstanceConfig(configRegistryClient, value + .getAttributes()), value.getProvidedServices().values()); + } + }); + } + }); + } + + @Override + protected String getOperationName() { + return GET_CONFIG; + } + + @Override + public Element handle(Document document, XmlElement xml) throws NetconfDocumentedException { + Datastore source; + try { + source = fromXml(xml); + } catch (final IllegalArgumentException e) { + logger.warn("Rpc error: {}", ErrorTag.bad_attribute, e); + final Map errorInfo = new HashMap<>(); + errorInfo.put(ErrorTag.bad_attribute.name(), e.getMessage()); + throw new NetconfDocumentedException(e.getMessage(), e, ErrorType.rpc, ErrorTag.bad_attribute, + ErrorSeverity.error, errorInfo); + } catch (final IllegalStateException e) { + logger.warn("Rpc error: {}", ErrorTag.missing_attribute, e); + final Map errorInfo = new HashMap<>(); + errorInfo.put(ErrorTag.missing_attribute.name(), "Missing datasource attribute value"); + throw new NetconfDocumentedException(e.getMessage(), e, ErrorType.rpc, ErrorTag.missing_attribute, + ErrorSeverity.error, errorInfo); + } catch (final UnsupportedOperationException e) { + logger.warn("Unsupported", e); + final Map errorInfo = new HashMap<>(); + errorInfo.put(ErrorTag.operation_not_supported.name(), "Unsupported option for get"); + throw new NetconfDocumentedException(e.getMessage(), e, ErrorType.application, + ErrorTag.operation_not_supported, ErrorSeverity.error, errorInfo); + } + return getResponseInternal(document, configRegistryClient, source); + } +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/getconfig/RunningDatastoreQueryStrategy.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/getconfig/RunningDatastoreQueryStrategy.java new file mode 100644 index 0000000000..c3f8257b1f --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/getconfig/RunningDatastoreQueryStrategy.java @@ -0,0 +1,24 @@ +/* + * 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.netconf.confignetconfconnector.operations.getconfig; + +import java.util.Set; + +import javax.management.ObjectName; + +import org.opendaylight.controller.config.util.ConfigRegistryClient; + +public class RunningDatastoreQueryStrategy implements DatastoreQueryStrategy { + + @Override + public Set queryInstances(ConfigRegistryClient configRegistryClient) { + return configRegistryClient.lookupConfigBeans(); + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/runtimerpc/RuntimeRpc.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/runtimerpc/RuntimeRpc.java new file mode 100644 index 0000000000..7bdfa277a0 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/runtimerpc/RuntimeRpc.java @@ -0,0 +1,248 @@ +/* + * 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.netconf.confignetconfconnector.operations.runtimerpc; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import org.opendaylight.controller.config.util.ConfigRegistryClient; +import org.opendaylight.controller.config.yang.store.api.YangStoreSnapshot; +import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry; +import org.opendaylight.controller.config.yangjmxgenerator.RuntimeBeanEntry; +import org.opendaylight.controller.config.yangjmxgenerator.RuntimeBeanEntry.Rpc; +import org.opendaylight.controller.config.yangjmxgenerator.attribute.SimpleTypeResolver; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.fromxml.AttributeConfigElement; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.mapping.SimpleAttributeMappingStrategy; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.rpc.InstanceRuntimeRpc; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.rpc.ModuleRpcs; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.rpc.Rpcs; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.AbstractConfigNetconfOperation; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.Commit; +import org.opendaylight.controller.netconf.mapping.api.HandlingPriority; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.management.ObjectName; +import javax.management.openmbean.SimpleType; +import java.util.Map; + +public class RuntimeRpc extends AbstractConfigNetconfOperation { + + private static final Logger logger = LoggerFactory.getLogger(Commit.class); + public static final String CONTEXT_INSTANCE = "context-instance"; + + private final YangStoreSnapshot yangStoreSnapshot; + + public RuntimeRpc(final YangStoreSnapshot yangStoreSnapshot, ConfigRegistryClient configRegistryClient, + String netconfSessionIdForReporting) { + super(configRegistryClient, netconfSessionIdForReporting); + this.yangStoreSnapshot = yangStoreSnapshot; + } + + private String getStringRepresentation(final Object result) { + final SimpleType simpleType = SimpleTypeResolver.getSimpleType(result.getClass().getName()); + final Optional mappedAttributeOpt = new SimpleAttributeMappingStrategy(simpleType).mapAttribute(result); + return mappedAttributeOpt.isPresent() ? mappedAttributeOpt.get() : ""; + } + + private Object executeOperation(final ConfigRegistryClient configRegistryClient, final ObjectName on, + final String name, final Map attributes) { + final Object[] params = new Object[attributes.size()]; + final String[] signature = new String[attributes.size()]; + + int i = 0; + for (final String attrName : attributes.keySet()) { + final AttributeConfigElement attribute = attributes.get(attrName); + final Optional resolvedValueOpt = attribute.getResolvedValue(); + + params[i] = resolvedValueOpt.isPresent() ? resolvedValueOpt.get() : attribute.getResolvedDefaultValue(); + signature[i] = resolvedValueOpt.isPresent() ? resolvedValueOpt.get().getClass().getName() : attribute + .getResolvedDefaultValue().getClass().getName(); + i++; + } + + return configRegistryClient.invokeMethod(on, name, params, signature); + } + + public NetconfOperationExecution fromXml(final XmlElement xml) throws NetconfDocumentedException { + final String namespace = xml.getNamespace(); + final XmlElement contextInstanceElement = xml.getOnlyChildElement(CONTEXT_INSTANCE); + final String operationName = xml.getName(); + + final RuntimeRpcElementResolved id = RuntimeRpcElementResolved.fromXpath( + contextInstanceElement.getTextContent(), operationName, namespace); + + final Rpcs rpcs = mapRpcs(yangStoreSnapshot.getModuleMXBeanEntryMap()); + + final ModuleRpcs rpcMapping = rpcs.getRpcMapping(id); + final InstanceRuntimeRpc instanceRuntimeRpc = rpcMapping.getRpc(id.getRuntimeBeanName(), operationName); + + // TODO move to Rpcs after xpath attribute is redesigned + + final ObjectName on = id.getObjectName(rpcMapping); + Map attributes = instanceRuntimeRpc.fromXml(xml); + attributes = sortAttributes(attributes, xml); + + return new NetconfOperationExecution(on, instanceRuntimeRpc.getName(), attributes, + instanceRuntimeRpc.getReturnType(), namespace); + } + + @Override + public HandlingPriority canHandle(Document message) { + XmlElement requestElement = getRequestElementWithCheck(message); + + XmlElement operationElement = requestElement.getOnlyChildElement(); + final String netconfOperationName = operationElement.getName(); + final String netconfOperationNamespace = operationElement.getNamespace(); + + final Optional contextInstanceElement = operationElement + .getOnlyChildElementOptionally(CONTEXT_INSTANCE); + + if (contextInstanceElement.isPresent() == false) + return HandlingPriority.CANNOT_HANDLE; + + final RuntimeRpcElementResolved id = RuntimeRpcElementResolved.fromXpath(contextInstanceElement.get() + .getTextContent(), netconfOperationName, netconfOperationNamespace); + + // TODO reuse rpcs instance in fromXml method + final Rpcs rpcs = mapRpcs(yangStoreSnapshot.getModuleMXBeanEntryMap()); + + try { + + final ModuleRpcs rpcMapping = rpcs.getRpcMapping(id); + final InstanceRuntimeRpc instanceRuntimeRpc = rpcMapping.getRpc(id.getRuntimeBeanName(), + netconfOperationName); + Preconditions.checkState(instanceRuntimeRpc != null, "No rpc found for %s:%s", netconfOperationNamespace, + netconfOperationName); + + } catch (IllegalStateException e) { + logger.debug("Cannot handle runtime operation {}:{}", netconfOperationNamespace, netconfOperationName, e); + return HandlingPriority.CANNOT_HANDLE; + } + + return HandlingPriority.HANDLE_WITH_DEFAULT_PRIORITY; + } + + @Override + protected HandlingPriority canHandle(String netconfOperationName, String namespace) { + throw new UnsupportedOperationException( + "This should not be used since it is not possible to provide check with these attributes"); + } + + @Override + protected String getOperationName() { + throw new UnsupportedOperationException("Runtime rpc does not have a stable name"); + } + + @Override + protected Element handle(Document document, XmlElement xml) throws NetconfDocumentedException { + + // TODO exception handling + // TODO check for namespaces and unknown elements + + final NetconfOperationExecution execution = fromXml(xml); + + logger.debug("Invoking operation {} on {} with arguments {}", execution.operationName, execution.on, + execution.attributes); + final Object result = executeOperation(configRegistryClient, execution.on, execution.operationName, + execution.attributes); + + logger.info("Operation {} called successfully on {} with arguments {} with result {}", execution.operationName, + execution.on, execution.attributes, result); + + if (execution.returnType.equals("void")) { + return document.createElement("ok"); + } else { + final Element output = XmlUtil.createTextElement(document, "result", getStringRepresentation(result)); + XmlUtil.addNamespaceAttr(output, execution.namespace); + return output; + } + } + + private static class NetconfOperationExecution { + + private final ObjectName on; + private final String operationName; + private final Map attributes; + private final String returnType; + private final String namespace; + + public NetconfOperationExecution(final ObjectName on, final String name, + final Map attributes, final String returnType, final String namespace) { + this.on = on; + this.operationName = name; + this.attributes = attributes; + this.returnType = returnType; + this.namespace = namespace; + } + + } + + private static Map sortAttributes( + final Map attributes, final XmlElement xml) { + final Map sorted = Maps.newLinkedHashMap(); + + for (XmlElement xmlElement : xml.getChildElements()) { + final String name = xmlElement.getName(); + if (CONTEXT_INSTANCE.equals(name) == false) { // skip context + // instance child node + // because it + // specifies + // ObjectName + final AttributeConfigElement value = attributes.get(name); + if (value == null) { + throw new IllegalArgumentException("Cannot find yang mapping for node " + xmlElement); + } + sorted.put(name, value); + } + } + + return sorted; + } + + private static Rpcs mapRpcs(final Map> mBeanEntries) { + + final Map> map = Maps.newHashMap(); + + for (final String namespace : mBeanEntries.keySet()) { + + Map namespaceToModules = map.get(namespace); + if (namespaceToModules == null) { + namespaceToModules = Maps.newHashMap(); + map.put(namespace, namespaceToModules); + } + + for (final String moduleName : mBeanEntries.get(namespace).keySet()) { + + ModuleRpcs rpcMapping = namespaceToModules.get(moduleName); + if (rpcMapping == null) { + rpcMapping = new ModuleRpcs(); + namespaceToModules.put(moduleName, rpcMapping); + } + + final ModuleMXBeanEntry entry = mBeanEntries.get(namespace).get(moduleName); + + for (final RuntimeBeanEntry runtimeEntry : entry.getRuntimeBeans()) { + rpcMapping.addNameMapping(runtimeEntry); + for (final Rpc rpc : runtimeEntry.getRpcs()) { + rpcMapping.addRpc(runtimeEntry, rpc); + } + } + } + } + + return new Rpcs(map); + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/runtimerpc/RuntimeRpcElementResolved.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/runtimerpc/RuntimeRpcElementResolved.java new file mode 100644 index 0000000000..838e5d8731 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/runtimerpc/RuntimeRpcElementResolved.java @@ -0,0 +1,107 @@ +/* + * 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.netconf.confignetconfconnector.operations.runtimerpc; + +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.common.collect.Maps; +import org.opendaylight.controller.config.api.jmx.ObjectNameUtil; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.rpc.ModuleRpcs; +import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; + +import javax.management.ObjectName; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Represents parsed xpath to runtime bean instance + */ +public final class RuntimeRpcElementResolved { + private final String moduleName; + private final String instanceName; + private final String namespace; + private final String runtimeBeanName; + private final Map additionalAttributes; + + private RuntimeRpcElementResolved(String namespace, String moduleName, String instanceName, String runtimeBeanName, + Map additionalAttributes) { + this.moduleName = moduleName; + this.instanceName = instanceName; + this.additionalAttributes = additionalAttributes; + this.namespace = namespace; + this.runtimeBeanName = runtimeBeanName; + } + + public String getModuleName() { + return moduleName; + } + + public String getInstanceName() { + return instanceName; + } + + public String getNamespace() { + return namespace; + } + + public String getRuntimeBeanName() { + return runtimeBeanName; + } + + public ObjectName getObjectName(ModuleRpcs rpcMapping) { + Map additionalAttributesJavaNames = Maps + .newHashMapWithExpectedSize(additionalAttributes.size()); + for (String attributeYangName : additionalAttributes.keySet()) { + String attributeJavaName = rpcMapping.getRbeJavaName(attributeYangName); + Preconditions.checkState(attributeJavaName != null, + "Cannot find java name for runtime bean wtih yang name %s", attributeYangName); + additionalAttributesJavaNames.put(attributeJavaName, additionalAttributes.get(attributeYangName)); + } + return ObjectNameUtil.createRuntimeBeanName(moduleName, instanceName, additionalAttributesJavaNames); + } + + private static final String xpathPatternBlueprint = "/" + XmlNetconfConstants.DATA_KEY + "/" + + XmlNetconfConstants.MODULES_KEY + "/" + XmlNetconfConstants.MODULE_KEY + "\\[" + + XmlNetconfConstants.NAME_KEY + "='(.+)'\\]/" + XmlNetconfConstants.INSTANCE_KEY + "\\[" + + XmlNetconfConstants.NAME_KEY + "='([^']+)'\\](.*)"; + private static final Pattern xpathPattern = Pattern.compile(xpathPatternBlueprint); + private static final String additionalPatternBlueprint = "(.+)\\[(.+)='(.+)'\\]"; + private static final Pattern additionalPattern = Pattern.compile(additionalPatternBlueprint); + + public static RuntimeRpcElementResolved fromXpath(String xpath, String elementName, String namespace) { + Matcher matcher = xpathPattern.matcher(xpath); + Preconditions.checkState(matcher.matches(), + "Node %s with value '%s' not in required form on rpc element %s, required format is %s", + RuntimeRpc.CONTEXT_INSTANCE, xpath, elementName, xpathPatternBlueprint); + + String moduleName = matcher.group(1); + String instanceName = matcher.group(2); + String additionalString = matcher.group(3); + HashMap additionalAttributes = Maps. newHashMap(); + String runtimeBeanYangName = moduleName; + for (String additionalKeyValue : additionalString.split("/")) { + if (Strings.isNullOrEmpty(additionalKeyValue)) + continue; + matcher = additionalPattern.matcher(additionalKeyValue); + Preconditions + .checkState( + matcher.matches(), + "Attribute %s not in required form on rpc element %s, required format for additional attributes is %s", + additionalKeyValue, elementName, additionalPatternBlueprint); + String name = matcher.group(1); + runtimeBeanYangName = name; + additionalAttributes.put(name, matcher.group(3)); + } + + return new RuntimeRpcElementResolved(namespace, moduleName, instanceName, runtimeBeanYangName, + additionalAttributes); + } +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/Activator.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/Activator.java new file mode 100644 index 0000000000..83029c44e6 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/Activator.java @@ -0,0 +1,55 @@ +/* + * 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.netconf.confignetconfconnector.osgi; + +import org.opendaylight.controller.config.yang.store.api.YangStoreService; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Hashtable; + +import static com.google.common.base.Preconditions.checkState; + +public class Activator implements BundleActivator, YangStoreServiceTracker.YangStoreTrackerListener { + + private static final Logger logger = LoggerFactory.getLogger(Activator.class); + + private BundleContext context; + ServiceRegistration osgiRegistration; + + @Override + public void start(BundleContext context) throws Exception { + this.context = context; + YangStoreServiceTracker tracker = new YangStoreServiceTracker(context, this); + tracker.open(); + } + + @Override + public void stop(BundleContext context) throws Exception { + } + + @Override + public synchronized void onYangStoreAdded(YangStoreService yangStoreService) { + checkState(osgiRegistration == null, "More than one onYangStoreAdded received"); + NetconfOperationServiceFactoryImpl factory = new NetconfOperationServiceFactoryImpl(yangStoreService); + logger.debug("Registering into OSGi"); + osgiRegistration = context.registerService(new String[]{NetconfOperationServiceFactory.class.getName()}, factory, + new Hashtable()); + } + + @Override + public synchronized void onYangStoreRemoved() { + osgiRegistration.unregister(); + osgiRegistration = null; + } +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationProvider.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationProvider.java new file mode 100644 index 0000000000..ec1915d6fc --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationProvider.java @@ -0,0 +1,67 @@ +/* + * 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.netconf.confignetconfconnector.osgi; + +import com.google.common.base.Optional; +import com.google.common.collect.Sets; +import org.opendaylight.controller.config.util.ConfigRegistryClient; +import org.opendaylight.controller.config.yang.store.api.YangStoreSnapshot; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.Commit; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.DiscardChanges; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.Validate; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.editconfig.EditConfig; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.get.Get; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.getconfig.GetConfig; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.runtimerpc.RuntimeRpc; +import org.opendaylight.controller.netconf.confignetconfconnector.transactions.TransactionProvider; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperation; + +import java.util.Set; + +final class NetconfOperationProvider { + private final YangStoreSnapshot yangStoreSnapshot; + private final Set operations; + private final ConfigRegistryClient configRegistryClient; + private final TransactionProvider transactionProvider; + + NetconfOperationProvider(YangStoreSnapshot yangStoreSnapshot, ConfigRegistryClient configRegistryClient, + TransactionProvider transactionProvider, String netconfSessionIdForReporting) { + + this.yangStoreSnapshot = yangStoreSnapshot; + this.configRegistryClient = configRegistryClient; + this.transactionProvider = transactionProvider; + operations = setUpOperations(yangStoreSnapshot, configRegistryClient, transactionProvider, + netconfSessionIdForReporting); + } + + Set getOperations() { + return operations; + } + + private static Set setUpOperations(YangStoreSnapshot yangStoreSnapshot, + ConfigRegistryClient configRegistryClient, TransactionProvider transactionProvider, + String netconfSessionIdForReporting) { + Set ops = Sets.newHashSet(); + + GetConfig getConfigOp = new GetConfig(yangStoreSnapshot, Optional. absent(), transactionProvider, + configRegistryClient, netconfSessionIdForReporting); + + ops.add(getConfigOp); + ops.add(new EditConfig(yangStoreSnapshot, transactionProvider, configRegistryClient, + netconfSessionIdForReporting)); + ops.add(new Commit(transactionProvider, configRegistryClient, netconfSessionIdForReporting)); + ops.add(new Get(yangStoreSnapshot, configRegistryClient, netconfSessionIdForReporting)); + ops.add(new DiscardChanges(transactionProvider, configRegistryClient, netconfSessionIdForReporting)); + ops.add(new Validate(transactionProvider, configRegistryClient, netconfSessionIdForReporting)); + ops.add(new RuntimeRpc(yangStoreSnapshot, configRegistryClient, netconfSessionIdForReporting)); + + return ops; + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationServiceFactoryImpl.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationServiceFactoryImpl.java new file mode 100644 index 0000000000..35695f7510 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationServiceFactoryImpl.java @@ -0,0 +1,66 @@ +/* + * 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.netconf.confignetconfconnector.osgi; + +import org.opendaylight.controller.config.util.ConfigRegistryJMXClient; +import org.opendaylight.controller.config.yang.store.api.YangStoreException; +import org.opendaylight.controller.config.yang.store.api.YangStoreService; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.management.MBeanServer; +import java.lang.management.ManagementFactory; + +public class NetconfOperationServiceFactoryImpl implements NetconfOperationServiceFactory { + + public static final int ATTEMPT_TIMEOUT_MS = 1000; + + private final YangStoreService yangStoreService; + private final ConfigRegistryJMXClient jmxClient; + + private static final Logger logger = LoggerFactory.getLogger(NetconfOperationServiceFactoryImpl.class); + + public NetconfOperationServiceFactoryImpl(YangStoreService yangStoreService) { + this(yangStoreService, ManagementFactory.getPlatformMBeanServer()); + } + + public NetconfOperationServiceFactoryImpl(YangStoreService yangStoreService, MBeanServer mBeanServer) { + this.yangStoreService = yangStoreService; + + // Config registry might not be present yet, but will be eventually + while(true) { + + final ConfigRegistryJMXClient configRegistryJMXClient; + try { + configRegistryJMXClient = new ConfigRegistryJMXClient(mBeanServer); + } catch (IllegalStateException e) { + logger.debug("Jmx client could not be created, reattempting"); + try { + Thread.sleep(ATTEMPT_TIMEOUT_MS); + } catch (InterruptedException e1) { + throw new RuntimeException(e1); + } + continue; + } + + jmxClient = configRegistryJMXClient; + break; + } + } + + @Override + public NetconfOperationServiceImpl createService(long netconfSessionId, String netconfSessionIdForReporting) { + try { + return new NetconfOperationServiceImpl(yangStoreService, jmxClient, netconfSessionIdForReporting); + } catch (YangStoreException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationServiceImpl.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationServiceImpl.java new file mode 100644 index 0000000000..8497edbd26 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationServiceImpl.java @@ -0,0 +1,158 @@ +/* + * 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.netconf.confignetconfconnector.osgi; + +import com.google.common.base.Optional; +import com.google.common.collect.Sets; +import org.opendaylight.controller.config.util.ConfigRegistryJMXClient; +import org.opendaylight.controller.config.yang.store.api.YangStoreException; +import org.opendaylight.controller.config.yang.store.api.YangStoreService; +import org.opendaylight.controller.config.yang.store.api.YangStoreSnapshot; +import org.opendaylight.controller.netconf.confignetconfconnector.transactions.TransactionProvider; +import org.opendaylight.controller.netconf.confignetconfconnector.util.Util; +import org.opendaylight.controller.netconf.mapping.api.Capability; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperation; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationFilter; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; +import org.opendaylight.yangtools.yang.model.api.Module; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +/** + * Manages life cycle of {@link YangStoreSnapshot}. + */ +public class NetconfOperationServiceImpl implements NetconfOperationService { + + private final YangStoreSnapshot yangStoreSnapshot; + private final NetconfOperationProvider operationProvider; + private final Set capabilities; + private final TransactionProvider transactionProvider; + + public NetconfOperationServiceImpl(YangStoreService yangStoreService, ConfigRegistryJMXClient jmxClient, + String netconfSessionIdForReporting) throws YangStoreException { + + yangStoreSnapshot = yangStoreService.getYangStoreSnapshot(); + transactionProvider = new TransactionProvider(jmxClient, netconfSessionIdForReporting); + operationProvider = new NetconfOperationProvider(yangStoreSnapshot, jmxClient, transactionProvider, + netconfSessionIdForReporting); + capabilities = setupCapabilities(yangStoreSnapshot); + } + + @Override + public void close() { + yangStoreSnapshot.close(); + transactionProvider.close(); + } + + @Override + public Set getCapabilities() { + return capabilities; + } + + @Override + public Set getNetconfOperations() { + return operationProvider.getOperations(); + } + + @Override + public Set getFilters() { + return Collections.emptySet(); + } + + private static Set setupCapabilities(YangStoreSnapshot yangStoreSnapshot) { + Set capabilities = Sets.newHashSet(); + + capabilities.add(new BasicCapability("urn:ietf:params:netconf:capability:candidate:1.0")); + capabilities.add(new BasicCapability("urn:ietf:params:netconf:capability:rollback-on-error:1.0")); + capabilities.add(new BasicCapability("urn:ietf:params:netconf:capability:operations:1.0")); + capabilities.add(new BasicCapability("urn:ietf:params:netconf:capability:operations:1.1")); + capabilities + .add(new BasicCapability( + "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring?module=ietf-netconf-monitoring&revision=2010-10-04")); + + final Collection> modulesAndContents = yangStoreSnapshot.getModuleMap().values(); + for (Map.Entry moduleAndContent : modulesAndContents) { + capabilities.add(new YangStoreCapability(moduleAndContent)); + } + + return capabilities; + } + + private static class BasicCapability implements Capability { + + private final String capability; + + private BasicCapability(String capability) { + this.capability = capability; + } + + @Override + public String getCapabilityUri() { + return capability; + } + + @Override + public Optional getModuleName() { + return Optional.absent(); + } + + @Override + public Optional getRevision() { + return Optional.absent(); + } + + @Override + public Optional getCapabilitySchema() { + return Optional.absent(); + } + } + + private static class YangStoreCapability extends BasicCapability { + + private final String content; + private final String revision; + private final String moduleName; + + public YangStoreCapability(Map.Entry moduleAndContent) { + super(getAsString(moduleAndContent.getKey())); + this.content = moduleAndContent.getValue(); + Module module = moduleAndContent.getKey(); + this.moduleName = module.getName(); + this.revision = Util.writeDate(module.getRevision()); + } + + @Override + public Optional getCapabilitySchema() { + return Optional.of(content); + } + + private static String getAsString(Module module) { + final StringBuffer capabilityContent = new StringBuffer(); + capabilityContent.append(module.getNamespace()); + capabilityContent.append("?module="); + capabilityContent.append(module.getName()); + capabilityContent.append("&revision="); + capabilityContent.append(Util.writeDate(module.getRevision())); + return capabilityContent.toString(); + } + + @Override + public Optional getModuleName() { + return Optional.of(moduleName); + } + + @Override + public Optional getRevision() { + return Optional.of(revision); + } + } +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/YangStoreServiceTracker.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/YangStoreServiceTracker.java new file mode 100644 index 0000000000..3b1e89d7df --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/YangStoreServiceTracker.java @@ -0,0 +1,41 @@ +/* + * 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.netconf.confignetconfconnector.osgi; + +import org.opendaylight.controller.config.yang.store.api.YangStoreService; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.util.tracker.ServiceTracker; + +class YangStoreServiceTracker extends ServiceTracker { + private final YangStoreTrackerListener listener; + + YangStoreServiceTracker(BundleContext context, final YangStoreTrackerListener listener) { + super(context, YangStoreService.class, null); + this.listener = listener; + } + + @Override + public synchronized YangStoreService addingService(final ServiceReference reference) { + final YangStoreService yangStoreService = super.addingService(reference); + listener.onYangStoreAdded(yangStoreService); + return yangStoreService; + } + + @Override + public synchronized void removedService(final ServiceReference reference, + final YangStoreService service) { + listener.onYangStoreRemoved(); + } + + static interface YangStoreTrackerListener { + void onYangStoreAdded(YangStoreService yangStoreService); + void onYangStoreRemoved(); + } +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/transactions/TransactionProvider.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/transactions/TransactionProvider.java new file mode 100644 index 0000000000..b3483a737a --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/transactions/TransactionProvider.java @@ -0,0 +1,168 @@ +/* + * 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.netconf.confignetconfconnector.transactions; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import org.opendaylight.controller.config.api.ValidationException; +import org.opendaylight.controller.config.api.jmx.CommitStatus; +import org.opendaylight.controller.config.util.ConfigRegistryClient; +import org.opendaylight.controller.config.util.ConfigTransactionClient; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.management.InstanceNotFoundException; +import javax.management.ObjectName; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class TransactionProvider implements AutoCloseable { + private static final Logger logger = LoggerFactory.getLogger(TransactionProvider.class); + + private final ConfigRegistryClient configRegistryClient; + + private final String netconfSessionIdForReporting; + private ObjectName transaction; + private final List allOpenedTransactions = new ArrayList<>(); + + public TransactionProvider(ConfigRegistryClient configRegistryClient, String netconfSessionIdForReporting) { + this.configRegistryClient = configRegistryClient; + this.netconfSessionIdForReporting = netconfSessionIdForReporting; + } + + @Override + public synchronized void close() { + for (ObjectName tx : allOpenedTransactions) { + if (isStillOpenTransaction(tx)) { + try { + configRegistryClient.getConfigTransactionClient(tx).abortConfig(); + } catch (Exception e) { + logger.debug("Ignoring {} while closing transaction {}", e.toString(), tx, e); + } + } + } + allOpenedTransactions.clear(); + } + + public Optional getTransaction() { + + if (transaction == null) + return Optional.absent(); + + // Transaction was already closed somehow + if (isStillOpenTransaction(transaction) == false) { + logger.warn("Fixing illegal state: transaction {} was closed in {}", transaction, + netconfSessionIdForReporting); + transaction = null; + return Optional.absent(); + } + return Optional.of(transaction); + } + + private boolean isStillOpenTransaction(ObjectName transaction) { + boolean isStillOpenTransaction = configRegistryClient.getOpenConfigs().contains(transaction); + return isStillOpenTransaction; + } + + public synchronized ObjectName getOrCreateTransaction() { + Optional ta = getTransaction(); + + if (ta.isPresent()) + return ta.get(); + transaction = configRegistryClient.beginConfig(); + allOpenedTransactions.add(transaction); + return transaction; + } + + /** + * Used for editConfig test option + */ + public synchronized ObjectName getTestTransaction() { + ObjectName testTx = configRegistryClient.beginConfig(); + allOpenedTransactions.add(testTx); + return testTx; + } + + /** + * Commit and notification send must be atomic + */ + public synchronized CommitStatus commitTransaction() throws NetconfDocumentedException { + final Optional taON = getTransaction(); + Preconditions.checkState(taON.isPresent(), "No transaction found for session " + netconfSessionIdForReporting); + CommitStatus status = configRegistryClient.commitConfig(taON.get()); + allOpenedTransactions.remove(transaction); + transaction = null; + return status; + } + + public synchronized void abortTransaction() { + Optional taON = getTransaction(); + Preconditions.checkState(taON.isPresent(), "No transaction found for session " + netconfSessionIdForReporting); + + ConfigTransactionClient transactionClient = configRegistryClient.getConfigTransactionClient(taON.get()); + transactionClient.abortConfig(); + allOpenedTransactions.remove(transaction); + transaction = null; + } + + public synchronized void abortTestTransaction(ObjectName testTx) { + ConfigTransactionClient transactionClient = configRegistryClient.getConfigTransactionClient(testTx); + allOpenedTransactions.remove(testTx); + transactionClient.abortConfig(); + } + + public void validateTransaction() throws ValidationException { + Optional taON = getTransaction(); + Preconditions.checkState(taON.isPresent(), "No transaction found for session " + netconfSessionIdForReporting); + + ConfigTransactionClient transactionClient = configRegistryClient.getConfigTransactionClient(taON.get()); + transactionClient.validateConfig(); + } + + public void validateTestTransaction(ObjectName taON) { + ConfigTransactionClient transactionClient = configRegistryClient.getConfigTransactionClient(taON); + transactionClient.validateConfig(); + } + + public void wipeTestTransaction(ObjectName taON) { + wipeInternal(taON, true, null); + } + + /** + * Wiping means removing all module instances keeping the transaction open. + */ + synchronized void wipeInternal(ObjectName taON, boolean isTest, String moduleName) { + ConfigTransactionClient transactionClient = configRegistryClient.getConfigTransactionClient(taON); + + Set lookupConfigBeans = moduleName == null ? transactionClient.lookupConfigBeans() + : transactionClient.lookupConfigBeans(moduleName); + for (ObjectName instance : lookupConfigBeans) { + try { + transactionClient.destroyModule(instance); + } catch (InstanceNotFoundException e) { + if (isTest) + logger.debug("Unable to clean configuration in transactiom {}", taON, e); + else + logger.warn("Unable to clean configuration in transactiom {}", taON, e); + + throw new IllegalStateException("Unable to clean configuration in transactiom " + taON, e); + } + } + logger.debug("Transaction {} wiped clean", taON); + } + + public void wipeTransaction() { + Optional taON = getTransaction(); + Preconditions.checkState(taON.isPresent(), "No transaction found for session " + netconfSessionIdForReporting); + wipeInternal(taON.get(), false, null); + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/util/Util.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/util/Util.java new file mode 100644 index 0000000000..26719592bb --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/util/Util.java @@ -0,0 +1,55 @@ +/* + * 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.netconf.confignetconfconnector.util; + +import com.google.common.base.Preconditions; +import org.opendaylight.controller.config.yang.store.api.YangStoreException; +import org.opendaylight.controller.config.yang.store.api.YangStoreService; +import org.opendaylight.controller.config.yang.store.api.YangStoreSnapshot; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorSeverity; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorTag; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorType; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +public final class Util { + + /** + * Used for date <-> xml serialization + */ + private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); + + public static String writeDate(final Date date) { + return dateFormat.format(date); + } + + public static Date readDate(final String s) throws ParseException { + return dateFormat.parse(s); + } + + public static void checkType(final Object value, final Class clazz) { + Preconditions.checkArgument(clazz.isAssignableFrom(value.getClass()), "Unexpected type " + value.getClass() + + " should be " + clazz); + } + + // TODO: add message and proper error types + public static YangStoreSnapshot getYangStore(final YangStoreService yangStoreService) + throws NetconfDocumentedException { + try { + return yangStoreService.getYangStoreSnapshot(); + } catch (final YangStoreException e) { + throw new NetconfDocumentedException("TODO", e, ErrorType.application, ErrorTag.bad_attribute, + ErrorSeverity.error); + } + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/NetconfMappingTest.java b/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/NetconfMappingTest.java new file mode 100644 index 0000000000..c72cb7498b --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/NetconfMappingTest.java @@ -0,0 +1,659 @@ +/* + * 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.netconf.confignetconfconnector; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.matchers.JUnitMatchers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.opendaylight.controller.config.api.ModuleIdentifier; +import org.opendaylight.controller.config.api.runtime.RootRuntimeBeanRegistrator; +import org.opendaylight.controller.config.manager.impl.AbstractConfigTest; +import org.opendaylight.controller.config.manager.impl.factoriesresolver.HardcodedModuleFactoriesResolver; +import org.opendaylight.controller.config.manager.impl.jmx.RootRuntimeBeanRegistratorImpl; +import org.opendaylight.controller.config.util.ConfigTransactionJMXClient; +import org.opendaylight.controller.config.yang.store.api.YangStoreSnapshot; +import org.opendaylight.controller.config.yang.store.impl.MbeParser; +import org.opendaylight.controller.config.yang.test.impl.*; +import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfOperationRouter; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.Commit; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.DiscardChanges; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.editconfig.EditConfig; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.get.Get; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.getconfig.GetConfig; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.runtimerpc.RuntimeRpc; +import org.opendaylight.controller.netconf.confignetconfconnector.transactions.TransactionProvider; +import org.opendaylight.controller.netconf.impl.mapping.operations.DefaultCloseSession; +import org.opendaylight.controller.netconf.mapping.api.HandlingPriority; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperation; +import org.opendaylight.controller.netconf.util.test.XmlFileLoader; +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.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.management.InstanceAlreadyExistsException; +import javax.management.ObjectName; +import javax.xml.parsers.ParserConfigurationException; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.util.*; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +public class NetconfMappingTest extends AbstractConfigTest { + private static final Logger logger = LoggerFactory.getLogger(NetconfMappingTest.class); + + private static final String INSTANCE_NAME = "test1"; + private static final String NETCONF_SESSION_ID = "foo"; + private NetconfTestImplModuleFactory factory; + private DepTestImplModuleFactory factory2; + + @Mock + YangStoreSnapshot yangStoreSnapshot; + @Mock + NetconfOperationRouter netconfOperationRouter; + + private TransactionProvider transactionProvider; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + doReturn(getMbes()).when(this.yangStoreSnapshot).getModuleMXBeanEntryMap(); + this.factory = new NetconfTestImplModuleFactory(); + this.factory2 = new DepTestImplModuleFactory(); + super.initConfigTransactionManagerImpl(new HardcodedModuleFactoriesResolver(this.factory, this.factory2)); + + transactionProvider = new TransactionProvider(this.configRegistryClient, NETCONF_SESSION_ID); + } + + private ObjectName createModule(final String instanceName) throws InstanceAlreadyExistsException { + final ConfigTransactionJMXClient transaction = this.configRegistryClient.createTransaction(); + + final ObjectName on = transaction.createModule(this.factory.getImplementationName(), instanceName); + final NetconfTestImplModuleMXBean mxBean = transaction.newMXBeanProxy(on, NetconfTestImplModuleMXBean.class); + setModule(mxBean, transaction); + + transaction.commit(); + return on; + } + + @Test + public void testConfigNetconf() throws Exception { + + createModule(INSTANCE_NAME); + + edit("netconfMessages/editConfig.xml"); + checkBinaryLeafEdited(getConfigCandidate()); + + // default-operation:none, should not affect binary leaf + edit("netconfMessages/editConfig_none.xml"); + checkBinaryLeafEdited(getConfigCandidate()); + + // check after edit + commit(); + Element response = getConfigRunning(); + // System.out.println(Xml.toString(response)); + + checkBinaryLeafEdited(response); + checkTypeConfigAttribute(response); + + edit("netconfMessages/editConfig_remove.xml"); + + commit(); + response = getConfigCandidate(); + final String responseFromCandidate = XmlUtil.toString(response).replaceAll("\\s+", ""); + // System.out.println(responseFromCandidate); + response = getConfigRunning(); + final String responseFromRunning = XmlUtil.toString(response).replaceAll("\\s+", ""); + // System.out.println(responseFromRunning); + assertEquals(responseFromCandidate, responseFromRunning); + + final String expectedResult = XmlFileLoader.fileToString("netconfMessages/editConfig_expectedResult.xml") + .replaceAll("\\s+", ""); + + assertEquals(expectedResult, responseFromRunning); + assertEquals(expectedResult, responseFromCandidate); + + edit("netconfMessages/editConfig_none.xml"); + doNothing().when(netconfOperationRouter).close(); + closeSession(); + verify(netconfOperationRouter).close(); + verifyNoMoreInteractions(netconfOperationRouter); + } + + private void closeSession() throws NetconfDocumentedException, ParserConfigurationException, SAXException, + IOException { + DefaultCloseSession closeOp = new DefaultCloseSession(NETCONF_SESSION_ID); + executeOp(closeOp, "netconfMessages/closeSession.xml"); + } + + private void edit(String resource) throws ParserConfigurationException, SAXException, IOException, + NetconfDocumentedException { + EditConfig editOp = new EditConfig(yangStoreSnapshot, transactionProvider, configRegistryClient, + NETCONF_SESSION_ID); + executeOp(editOp, resource); + } + + private void commit() throws ParserConfigurationException, SAXException, IOException, NetconfDocumentedException { + Commit commitOp = new Commit(transactionProvider, configRegistryClient, NETCONF_SESSION_ID); + executeOp(commitOp, "netconfMessages/commit.xml"); + } + + private Element getConfigCandidate() throws ParserConfigurationException, SAXException, IOException, + NetconfDocumentedException { + GetConfig getConfigOp = new GetConfig(yangStoreSnapshot, Optional. absent(), transactionProvider, + configRegistryClient, NETCONF_SESSION_ID); + return executeOp(getConfigOp, "netconfMessages/getConfig_candidate.xml"); + } + + private Element getConfigRunning() throws ParserConfigurationException, SAXException, IOException, + NetconfDocumentedException { + GetConfig getConfigOp = new GetConfig(yangStoreSnapshot, Optional. absent(), transactionProvider, + configRegistryClient, NETCONF_SESSION_ID); + return executeOp(getConfigOp, "netconfMessages/getConfig.xml"); + } + + @Test(expected = NetconfDocumentedException.class) + public void testConfigNetconfReplaceDefaultEx() throws Exception { + + createModule(INSTANCE_NAME); + + edit("netconfMessages/editConfig.xml"); + edit("netconfMessages/editConfig_replace_default_ex.xml"); + } + + @Test + public void testConfigNetconfReplaceDefault() throws Exception { + + createModule(INSTANCE_NAME); + + edit("netconfMessages/editConfig.xml"); + commit(); + Element response = getConfigRunning(); + final int allInstances = response.getElementsByTagName("module").getLength(); + + edit("netconfMessages/editConfig_replace_default.xml"); + + commit(); + response = getConfigRunning(); + + final int afterReplace = response.getElementsByTagName("module").getLength(); + assertEquals(4, allInstances); + assertEquals(2, afterReplace); + } + + @Test(expected = NetconfDocumentedException.class) + public void testSameAttrDifferentNamespaces() throws Exception { + try { + edit("netconfMessages/namespaces/editConfig_sameAttrDifferentNamespaces.xml"); + } catch (NetconfDocumentedException e) { + String message = e.getMessage(); + assertThat(message, + JUnitMatchers + .containsString("Element simple-long-2 present multiple times with different namespaces")); + assertThat(message, + JUnitMatchers.containsString("urn:opendaylight:params:xml:ns:yang:controller:test:impl")); + assertThat(message, + JUnitMatchers + .containsString(XmlNetconfConstants.URN_OPENDAYLIGHT_PARAMS_XML_NS_YANG_CONTROLLER_CONFIG)); + throw e; + } + } + + @Test(expected = NetconfDocumentedException.class) + public void testDifferentNamespaceInTO() throws Exception { + try { + edit("netconfMessages/namespaces/editConfig_differentNamespaceTO.xml"); + } catch (NetconfDocumentedException e) { + String message = e.getMessage(); + assertThat(message, JUnitMatchers.containsString("Unrecognised elements")); + assertThat(message, JUnitMatchers.containsString("simple-int2")); + assertThat(message, JUnitMatchers.containsString("dto_d")); + throw e; + } + } + + @Test(expected = NetconfDocumentedException.class) + public void testSameAttrDifferentNamespacesList() throws Exception { + try { + edit("netconfMessages/namespaces/editConfig_sameAttrDifferentNamespacesList.xml"); + } catch (NetconfDocumentedException e) { + String message = e.getMessage(); + assertThat(message, + JUnitMatchers.containsString("Element binaryLeaf present multiple times with different namespaces")); + assertThat(message, + JUnitMatchers.containsString("urn:opendaylight:params:xml:ns:yang:controller:test:impl")); + assertThat(message, + JUnitMatchers + .containsString(XmlNetconfConstants.URN_OPENDAYLIGHT_PARAMS_XML_NS_YANG_CONTROLLER_CONFIG)); + throw e; + } + } + + @Test + public void testTypeNameConfigAttributeMatching() throws Exception { + edit("netconfMessages/editConfig.xml"); + commit(); + edit("netconfMessages/namespaces/editConfig_typeNameConfigAttributeMatching.xml"); + commit(); + + Element response = getConfigRunning(); + checkTypeConfigAttribute(response); + } + + // TODO add functionality + @Test(expected = NetconfDocumentedException.class) + public void testConfigNetconfReplaceModuleEx() throws Exception { + + createModule(INSTANCE_NAME); + + edit("netconfMessages/editConfig.xml"); + edit("netconfMessages/editConfig_replace_module_ex.xml"); + } + + @Test + public void testUnrecognisedConfigElements() throws Exception { + + String format = "netconfMessages/unrecognised/editConfig_unrecognised%d.xml"; + final int TESTS_COUNT = 8; + + for (int i = 0; i < TESTS_COUNT; i++) { + String file = String.format(format, i + 1); + try { + edit(file); + } catch (NetconfDocumentedException e) { + Assert.assertThat(e.getMessage(), JUnitMatchers.containsString("Unrecognised elements")); + Assert.assertThat(e.getMessage(), JUnitMatchers.containsString("unknownAttribute")); + continue; + } + fail("Unrecognised test should throw exception " + file); + } + } + + @Test + @Ignore + // FIXME + public void testConfigNetconfReplaceModule() throws Exception { + + createModule(INSTANCE_NAME); + + edit("netconfMessages/editConfig.xml"); + commit(); + Element response = getConfigRunning(); + final int allInstances = response.getElementsByTagName("instance").getLength(); + + edit("netconfMessages/editConfig_replace_module.xml"); + + commit(); + response = getConfigRunning(); + final int afterReplace = response.getElementsByTagName("instance").getLength(); + + Assert.assertEquals(4 + 4 /* Instances from services */, allInstances); + Assert.assertEquals(3 + 3, afterReplace); + } + + @Test(expected = NetconfDocumentedException.class) + public void testEx() throws Exception { + + commit(); + } + + @Test(expected = NetconfDocumentedException.class) + public void testEx2() throws Exception { + discard(); + } + + private void discard() throws ParserConfigurationException, SAXException, IOException, NetconfDocumentedException { + DiscardChanges discardOp = new DiscardChanges(transactionProvider, configRegistryClient, NETCONF_SESSION_ID); + executeOp(discardOp, "netconfMessages/discardChanges.xml"); + } + + private void checkBinaryLeafEdited(final Element response) { + final NodeList children = response.getElementsByTagName("binaryLeaf"); + assertEquals(3, children.getLength()); + final StringBuffer buf = new StringBuffer(); + for (int i = 0; i < 3; i++) { + final Element e = (Element) children.item(i); + buf.append(XmlElement.fromDomElement(e).getTextContent()); + } + assertEquals("810", buf.toString()); + + } + + private void checkTypeConfigAttribute(Element response) { + + XmlElement modulesElement = XmlElement.fromDomElement(response).getOnlyChildElement("data") + .getOnlyChildElement("modules"); + + XmlElement configAttributeType = null; + for (XmlElement moduleElement : modulesElement.getChildElements("module")) { + for (XmlElement type : moduleElement.getChildElements("type")) { + if (type.getAttribute(XmlUtil.XMLNS_ATTRIBUTE_KEY).equals("") == false) { + configAttributeType = type; + } + } + } + + assertEquals("configAttributeType", configAttributeType.getTextContent()); + } + + private Map> getMbes() throws Exception { + final List yangDependencies = getYangs(); + + final Map> mBeanEntries = Maps.newHashMap(); + mBeanEntries.putAll(new MbeParser().parseYangFiles(yangDependencies).getModuleMXBeanEntryMap()); + + return mBeanEntries; + } + + @Test + public void testConfigNetconfRuntime() throws Exception { + + ModuleIdentifier id = new ModuleIdentifier(NetconfTestImplModuleFactory.NAME, "instance"); + RootRuntimeBeanRegistrator rootReg = new RootRuntimeBeanRegistratorImpl(internalJmxRegistrator, id); + NetconfTestImplRuntimeRegistrator registrator = new NetconfTestImplRuntimeRegistrator(rootReg); + + NetconfTestImplRuntimeRegistration a = registerRoot(registrator); + InnerRunningDataRuntimeRegistration reg = registerInner(a); + registerInner2(reg); + + id = new ModuleIdentifier(NetconfTestImplModuleFactory.NAME, "instance2"); + rootReg = new RootRuntimeBeanRegistratorImpl(internalJmxRegistrator, id); + registrator = new NetconfTestImplRuntimeRegistrator(rootReg); + + a = registerRoot(registrator); + registerAdditional(a); + registerAdditional(a); + registerAdditional(a); + registerAdditional(a); + reg = registerInner(a); + registerInner2(reg); + reg = registerInner(a); + registerInner2(reg); + registerInner2(reg); + reg = registerInner(a); + registerInner2(reg); + registerInner2(reg); + registerInner2(reg); + reg = registerInner(a); + registerInner2(reg); + registerInner2(reg); + registerInner2(reg); + registerInner2(reg); + + Element response = get(); + + assertEquals(2, getElementsSize(response, "instance")); + assertEquals(2, getElementsSize(response, "asdf")); + assertEquals(5, getElementsSize(response, "inner-running-data")); + assertEquals(5, getElementsSize(response, "deep2")); + assertEquals(11, getElementsSize(response, "inner-inner-running-data")); + assertEquals(11, getElementsSize(response, "deep3")); + assertEquals(4, getElementsSize(response, "inner-running-data-additional")); + assertEquals(4, getElementsSize(response, "deep4")); + // TODO assert keys + + RuntimeRpc netconf = new RuntimeRpc(yangStoreSnapshot, configRegistryClient, NETCONF_SESSION_ID); + + response = executeOp(netconf, "netconfMessages/rpc.xml"); + assertThat(XmlUtil.toString(response), JUnitMatchers.containsString("testarg1".toUpperCase())); + + response = executeOp(netconf, "netconfMessages/rpcInner.xml"); + assertThat(XmlUtil.toString(response), JUnitMatchers.containsString("ok")); + + response = executeOp(netconf, "netconfMessages/rpcInnerInner.xml"); + assertThat(XmlUtil.toString(response), JUnitMatchers.containsString("true")); + } + + private Element get() throws NetconfDocumentedException, ParserConfigurationException, SAXException, IOException { + Get getOp = new Get(yangStoreSnapshot, configRegistryClient, NETCONF_SESSION_ID); + return executeOp(getOp, "netconfMessages/get.xml"); + } + + private int getElementsSize(Element response, String elementName) { + return response.getElementsByTagName(elementName).getLength(); + } + + private Object registerAdditional(final NetconfTestImplRuntimeRegistration a) { + class InnerRunningDataAdditionalRuntimeMXBeanTest implements InnerRunningDataAdditionalRuntimeMXBean { + + private final int simpleInt; + private final String simpleString; + + public InnerRunningDataAdditionalRuntimeMXBeanTest(final int simpleInt, final String simpleString) { + this.simpleInt = simpleInt; + this.simpleString = simpleString; + } + + @Override + public Integer getSimpleInt3() { + return this.simpleInt; + } + + @Override + public Deep4 getDeep4() { + final Deep4 d = new Deep4(); + d.setBoool(false); + return d; + } + + @Override + public String getSimpleString() { + return this.simpleString; + } + + @Override + public void noArgInner() { + } + + } + + final int simpleInt = counter++; + return a.register(new InnerRunningDataAdditionalRuntimeMXBeanTest(simpleInt, "randomString_" + simpleInt)); + } + + private void registerInner2(final InnerRunningDataRuntimeRegistration reg) { + class InnerInnerRunningDataRuntimeMXBeanTest implements InnerInnerRunningDataRuntimeMXBean { + + private final int simpleInt; + + public InnerInnerRunningDataRuntimeMXBeanTest(final int simpleInt) { + this.simpleInt = simpleInt; + } + + @Override + public List getNotStateBean() { + final NotStateBean notStateBean = new NotStateBean(); + final NotStateBeanInternal notStateBeanInternal = new NotStateBeanInternal(); + notStateBean.setNotStateBeanInternal(Lists.newArrayList(notStateBeanInternal)); + return Lists.newArrayList(notStateBean); + } + + @Override + public Integer getSimpleInt3() { + return this.simpleInt; + } + + @Override + public Deep3 getDeep3() { + return new Deep3(); + } + + @Override + public Boolean noArgInnerInner(Integer integer, Boolean aBoolean) { + return aBoolean; + } + + } + + reg.register(new InnerInnerRunningDataRuntimeMXBeanTest(counter++)); + + } + + private static int counter = 1000; + + private InnerRunningDataRuntimeRegistration registerInner(final NetconfTestImplRuntimeRegistration a) { + + class InnerRunningDataRuntimeMXBeanTest implements InnerRunningDataRuntimeMXBean { + + private final int simpleInt; + + public InnerRunningDataRuntimeMXBeanTest(final int simpleInt) { + this.simpleInt = simpleInt; + } + + @Override + public Integer getSimpleInt3() { + return this.simpleInt; + } + + @Override + public Deep2 getDeep2() { + return new Deep2(); + } + + } + return a.register(new InnerRunningDataRuntimeMXBeanTest(counter++)); + } + + private NetconfTestImplRuntimeRegistration registerRoot(final NetconfTestImplRuntimeRegistrator registrator) { + final NetconfTestImplRuntimeRegistration a = registrator.register(new NetconfTestImplRuntimeMXBean() { + + @Override + public Long getCreatedSessions() { + return 11L; + } + + @Override + public Asdf getAsdf() { + final Asdf asdf = new Asdf(); + asdf.setSimpleInt(55); + asdf.setSimpleString("asdf"); + return asdf; + } + + @Override + public String noArg(final String arg1) { + return arg1.toUpperCase(); + } + + }); + return a; + } + + private Element executeOp(final NetconfOperation op, final String filename) throws ParserConfigurationException, + SAXException, IOException, NetconfDocumentedException { + + final Document request = XmlFileLoader.xmlFileToDocument(filename); + + logger.debug("Executing netconf operation\n{}", XmlUtil.toString(request)); + HandlingPriority priority = op.canHandle(request); + + Preconditions.checkState(priority != HandlingPriority.CANNOT_HANDLE); + + final Document response = op.handle(request, netconfOperationRouter); + logger.debug("Got response\n{}", XmlUtil.toString(response)); + return response.getDocumentElement(); + } + + private List getYangs() throws FileNotFoundException { + List paths = Arrays.asList("/META-INF/yang/config.yang", "/META-INF/yang/rpc-context.yang", + "/META-INF/yang/config-test.yang", "/META-INF/yang/config-test-impl.yang", + "/META-INF/yang/ietf-inet-types.yang"); + final Collection yangDependencies = new ArrayList<>(); + for (String path : paths) { + final InputStream is = Preconditions + .checkNotNull(getClass().getResourceAsStream(path), path + " not found"); + yangDependencies.add(is); + } + return Lists.newArrayList(yangDependencies); + } + + private void setModule(final NetconfTestImplModuleMXBean mxBean, final ConfigTransactionJMXClient transaction) + throws InstanceAlreadyExistsException { + mxBean.setSimpleInt((long) 44); + mxBean.setBinaryLeaf(new byte[] { 8, 7, 9 }); + final DtoD dtob = getDtoD(); + mxBean.setDtoD(dtob); + // + final DtoC dtoa = getDtoC(); + mxBean.setDtoC(dtoa); + mxBean.setSimpleBoolean(false); + // + final Peers p1 = new Peers(); + p1.setCoreSize(44L); + p1.setPort("port1"); + p1.setSimpleInt3(456); + final Peers p2 = new Peers(); + p2.setCoreSize(44L); + p2.setPort("port23"); + p2.setSimpleInt3(456); + mxBean.setPeers(Lists. newArrayList(p1, p2)); + // // + mxBean.setSimpleLong(454545L); + mxBean.setSimpleLong2(44L); + mxBean.setSimpleBigInteger(BigInteger.valueOf(999L)); + mxBean.setSimpleByte(new Byte((byte) 4)); + mxBean.setSimpleShort(new Short((short) 4)); + mxBean.setSimpleTest(545); + + mxBean.setComplexList(Lists. newArrayList()); + mxBean.setSimpleList(Lists. newArrayList()); + + final ObjectName testingDepOn = transaction.createModule(this.factory2.getImplementationName(), "dep"); + mxBean.setTestingDep(testingDepOn); + } + + private static DtoD getDtoD() { + final DtoD dtob = new DtoD(); + dtob.setSimpleInt1((long) 444); + dtob.setSimpleInt2((long) 4444); + dtob.setSimpleInt3(454); + final ComplexDtoBInner dtobInner = new ComplexDtoBInner(); + final Deep deep = new Deep(); + deep.setSimpleInt3(4); + dtobInner.setDeep(deep); + dtobInner.setSimpleInt3(44); + dtobInner.setSimpleList(Lists.newArrayList(4)); + dtob.setComplexDtoBInner(Lists.newArrayList(dtobInner)); + dtob.setSimpleList(Lists.newArrayList(4)); + return dtob; + } + + private static DtoC getDtoC() { + final DtoC dtoa = new DtoC(); + // dtoa.setSimpleArg((long) 55); + final DtoAInner dtoAInner = new DtoAInner(); + final DtoAInnerInner dtoAInnerInner = new DtoAInnerInner(); + dtoAInnerInner.setSimpleArg(456L); + dtoAInner.setDtoAInnerInner(dtoAInnerInner); + dtoAInner.setSimpleArg(44L); + dtoa.setDtoAInner(dtoAInner); + return dtoa; + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/ServiceTrackerTest.java b/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/ServiceTrackerTest.java new file mode 100644 index 0000000000..e07f9ce3e1 --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/ServiceTrackerTest.java @@ -0,0 +1,71 @@ +/* + * 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.netconf.confignetconfconnector; + +import org.junit.Test; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config.Services; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config.Services.ServiceInstance; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class ServiceTrackerTest { + + @Test + public void test() { + Services.ServiceInstance serviceInstance = new ServiceInstance("module", "serviceInstance"); + + String string = serviceInstance.toString(); + + Services.ServiceInstance serviceInstance2 = Services.ServiceInstance.fromString(string); + + assertEquals(serviceInstance, serviceInstance2); + } + + @Test + public void testOneInstanceMultipleServices() { + Services services = new Services(); + services.addServiceEntry("s1", "module", "instance"); + assertEquals(1, services.getMappedServices().size()); + + services.addServiceEntry("s2", "module", "instance"); + assertEquals(2, services.getMappedServices().size()); + } + + @Test + public void testMultipleInstancesOneName() throws Exception { + Services services = new Services(); + services.addServiceEntry("s1", "module", "instance"); + assertEquals(1, services.getMappedServices().size()); + + services.addServiceEntry("s1", "module2", "instance"); + assertEquals(1, services.getMappedServices().size()); + assertEquals(2, services.getMappedServices().get("s1").size()); + assertTrue(services.getMappedServices().get("s1").containsKey("ref_instance")); + assertTrue(services.getMappedServices().get("s1").containsKey("ref_instance_1")); + } + + @Test + public void testMultipleInstancesOneName2() throws Exception { + Services services = new Services(); + services.addServiceEntry("s1", "module", "instance_1"); + + services.addServiceEntry("s2", "module2", "instance"); + services.addServiceEntry("s2", "module3", "instance"); + services.addServiceEntry("s1", "module3", "instance"); + + assertEquals(2, services.getMappedServices().get("s1").size()); + assertEquals(2, services.getMappedServices().get("s2").size()); + assertTrue(services.getMappedServices().get("s1").containsKey("ref_instance_2")); + assertTrue(services.getMappedServices().get("s1").containsKey("ref_instance_1")); + assertTrue(services.getMappedServices().get("s2").containsKey("ref_instance")); + assertTrue(services.getMappedServices().get("s2").containsKey("ref_instance_2")); + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/ValidateTest.java b/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/ValidateTest.java new file mode 100644 index 0000000000..8d14e9680c --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/ValidateTest.java @@ -0,0 +1,94 @@ +/* + * 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.netconf.confignetconfconnector.operations; + +import org.junit.Test; +import org.opendaylight.controller.config.api.ValidationException; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.confignetconfconnector.transactions.TransactionProvider; +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.w3c.dom.Element; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.*; + +public class ValidateTest { + + public static final String NETCONF_SESSION_ID_FOR_REPORTING = "foo"; + + @Test(expected = NetconfDocumentedException.class) + public void test() throws Exception { + final XmlElement xml = XmlElement.fromString(""); + final Validate validate = new Validate(null, null, NETCONF_SESSION_ID_FOR_REPORTING); + validate.handle(null, xml); + } + + @Test(expected = NetconfDocumentedException.class) + public void testNoSource() throws Exception { + final XmlElement xml = XmlElement.fromString(""); + final Validate validate = new Validate(null, null, NETCONF_SESSION_ID_FOR_REPORTING); + validate.handle(null, xml); + } + + @Test(expected = NetconfDocumentedException.class) + public void testNoNamespace() throws Exception { + final XmlElement xml = XmlElement.fromString(""); + final Validate validate = new Validate(null, null, NETCONF_SESSION_ID_FOR_REPORTING); + validate.handle(null, xml); + } + + @Test(expected = NetconfDocumentedException.class) + public void testRunningSource() throws Exception { + + final XmlElement xml = XmlElement.fromString(""); + final Validate validate = new Validate(null, null, NETCONF_SESSION_ID_FOR_REPORTING); + validate.handle(null, xml); + } + + @Test(expected = NetconfDocumentedException.class) + public void testNoTransaction() throws Exception { + final XmlElement xml = XmlElement.fromString(""); + final TransactionProvider transactionProvider = mock(TransactionProvider.class); + doThrow(IllegalStateException.class).when(transactionProvider).validateTransaction(); + final Validate validate = new Validate(transactionProvider, null, NETCONF_SESSION_ID_FOR_REPORTING); + validate.handle(null, xml); + } + + @Test(expected = NetconfDocumentedException.class) + public void testValidationException() throws Exception { + final XmlElement xml = XmlElement.fromString(">"); + final TransactionProvider transactionProvider = mock(TransactionProvider.class); + doThrow(ValidationException.class).when(transactionProvider).validateTransaction(); + final Validate validate = new Validate(transactionProvider, null, NETCONF_SESSION_ID_FOR_REPORTING); + validate.handle(null, xml); + } + + @Test + public void testValidation() throws Exception { + final XmlElement xml = XmlElement.fromString(""); + final TransactionProvider transactionProvider = mock(TransactionProvider.class); + final Element okElement = XmlUtil.readXmlToElement(""); + doNothing().when(transactionProvider).validateTransaction(); + final Validate validate = new Validate(transactionProvider, null, NETCONF_SESSION_ID_FOR_REPORTING); + Element ok = validate.handle(XmlUtil.newDocument(), xml); + assertEquals(XmlUtil.toString(okElement), XmlUtil.toString(ok)); + } + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/EditConfigTest.java b/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/EditConfigTest.java new file mode 100644 index 0000000000..f43caf641f --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/EditConfigTest.java @@ -0,0 +1,136 @@ +/* + * 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.netconf.confignetconfconnector.operations.editconfig; + +import com.google.common.base.Optional; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.opendaylight.controller.config.util.ConfigRegistryClient; +import org.opendaylight.controller.config.util.ConfigTransactionClient; +import org.opendaylight.controller.config.yang.store.api.YangStoreSnapshot; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.fromxml.AttributeConfigElement; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config.Config; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config.InstanceConfigElementResolved; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config.ModuleElementResolved; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.editconfig.EditConfig; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.editconfig.EditConfigStrategy; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.editconfig.EditConfigXmlParser; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.editconfig.EditConfigXmlParser.EditConfigExecution; +import org.opendaylight.controller.netconf.confignetconfconnector.transactions.TransactionProvider; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.ValidateTest; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; + +import javax.management.ObjectName; +import java.util.Map; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyMap; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.*; + +public class EditConfigTest { + + @Mock + private YangStoreSnapshot yangStoreSnapshot; + @Mock + private TransactionProvider provider; + @Mock + private ConfigRegistryClient configRegistry; + @Mock + private ConfigTransactionClient configTransactionClient; + @Mock + private ObjectName mockOn; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + doReturn("mockON").when(mockOn).toString(); + doReturn(mockOn).when(provider).getTestTransaction(); + doNothing().when(provider).validateTestTransaction(any(ObjectName.class)); + + doReturn(mockOn).when(provider).getTestTransaction(); + doNothing().when(provider).abortTestTransaction(any(ObjectName.class)); + doReturn(mockOn).when(provider).getOrCreateTransaction(); + + doReturn(configTransactionClient).when(configRegistry).getConfigTransactionClient(any(ObjectName.class)); + doReturn("mockConfigTransactionClient").when(configTransactionClient).toString(); + + doReturn(mockOn).when(configTransactionClient).lookupConfigBean(anyString(), anyString()); + } + + @Test + public void test() throws NetconfDocumentedException { + EditConfig edit = new EditConfig(yangStoreSnapshot, provider, configRegistry, + ValidateTest.NETCONF_SESSION_ID_FOR_REPORTING); + EditConfigStrategy editStrat = mock(EditConfigStrategy.class); + doNothing().when(editStrat).executeConfiguration(anyString(), anyString(), anyMap(), + any(ConfigTransactionClient.class)); + Map> resolvedXmlElements = getMapping(editStrat); + + Config cfg = mock(Config.class); + XmlElement xmlElement = mock(XmlElement.class); + doReturn(resolvedXmlElements).when(cfg).fromXml(xmlElement); + + EditConfigExecution editConfigExecution = new EditConfigExecution(null, cfg, xmlElement, + EditConfigXmlParser.TestOption.testThenSet); + + edit.getResponseInternal(XmlUtil.newDocument(), editConfigExecution); + + verify(provider).getTestTransaction(); + verify(provider).validateTestTransaction(mockOn); + verify(provider).abortTestTransaction(mockOn); + + verify(provider).getOrCreateTransaction(); + + // For every instance execute strat + verify(editStrat, times(2/* Test */+ 2/* Set */)).executeConfiguration(anyString(), anyString(), anyMap(), + any(ConfigTransactionClient.class)); + } + + private Map> getMapping(EditConfigStrategy editStrat) { + Map> result = Maps.newHashMap(); + + Multimap innerMultimap = HashMultimap.create(); + Map attributes = getSimpleAttributes(); + + InstanceConfigElementResolved ice1 = mock(InstanceConfigElementResolved.class); + doReturn(attributes).when(ice1).getConfiguration(); + doReturn(editStrat).when(ice1).getEditStrategy(); + innerMultimap.put("m1", new ModuleElementResolved("i1", ice1)); + + InstanceConfigElementResolved ice2 = mock(InstanceConfigElementResolved.class); + doReturn(attributes).when(ice2).getConfiguration(); + doReturn(editStrat).when(ice2).getEditStrategy(); + innerMultimap.put("m1", new ModuleElementResolved("i2", ice2)); + + result.put("n1", innerMultimap); + + return result; + } + + static Map getSimpleAttributes() { + Map attributes = Maps.newHashMap(); + AttributeConfigElement ace1 = mock(AttributeConfigElement.class); + doReturn("abcd").when(ace1).getResolvedDefaultValue(); + doReturn(Optional. of("abc")).when(ace1).getResolvedValue(); + doReturn("mockedAce1").when(ace1).toString(); + doReturn("jmxNameAce1").when(ace1).getJmxName(); + attributes.put("a1", ace1); + return attributes; + } + +} 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 new file mode 100644 index 0000000000..13421a215e --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/ReplaceEditConfigStrategyTest.java @@ -0,0 +1,64 @@ +/* + * 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.netconf.confignetconfconnector.operations.editconfig; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.*; + +import java.util.Map; + +import javax.management.Attribute; +import javax.management.ObjectName; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.opendaylight.controller.config.util.ConfigTransactionClient; +import org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes.fromxml.AttributeConfigElement; + +import com.google.common.collect.Sets; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.editconfig.EditConfigTest; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.editconfig.ReplaceEditConfigStrategy; + +public class ReplaceEditConfigStrategyTest { + + @Mock + private ConfigTransactionClient ta; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + doNothing().when(ta).destroyConfigBean(anyString(), anyString()); + doReturn(mockON()).when(ta).lookupConfigBean(anyString(), anyString()); + doNothing().when(ta).setAttribute(any(ObjectName.class), anyString(), any(Attribute.class)); + } + + @Test + public void test() throws Exception { + ReplaceEditConfigStrategy strat = new ReplaceEditConfigStrategy(); + + Map map = EditConfigTest.getSimpleAttributes(); + + doReturn(Sets.newHashSet(mockON(), mockON())).when(ta).lookupConfigBeans(); + + strat.executeConfiguration("m1", "i1", map, ta); + + verify(ta).lookupConfigBean(anyString(), anyString()); + verify(ta).setAttribute(any(ObjectName.class), anyString(), any(Attribute.class)); + } + + ObjectName mockON() { + ObjectName mock = mock(ObjectName.class); + doReturn("mockON").when(mock).toString(); + return mock; + } + +} diff --git a/opendaylight/netconf/config-persister-impl/pom.xml b/opendaylight/netconf/config-persister-impl/pom.xml new file mode 100644 index 0000000000..c86202b6fb --- /dev/null +++ b/opendaylight/netconf/config-persister-impl/pom.xml @@ -0,0 +1,98 @@ + + 4.0.0 + + + netconf-subsystem + org.opendaylight.controller + 0.2.1-SNAPSHOT + + config-persister-impl + ${project.artifactId} + bundle + + + + + org.opendaylight.controller + config-persister-api + 0.2.1-SNAPSHOT + + + ${project.groupId} + netconf-api + ${project.version} + + + ${project.groupId} + netconf-client + ${project.version} + + + ${project.groupId} + netconf-util + ${project.version} + + + org.slf4j + slf4j-api + + + com.google.guava + guava + + + org.osgi + org.osgi.core + + + org.opendaylight.controller + config-persister-file-adapter + 0.2.1-SNAPSHOT + + + + + org.opendaylight.bgpcep + mockito-configuration + test + + + + + + + org.apache.felix + maven-bundle-plugin + + + org.opendaylight.controller.netconf.persist.impl.osgi.ConfigPersisterActivator + + org.opendaylight.controller.config.persister.storage.adapter + + + com.google.common.base, + com.google.common.collect, + javax.management, + javax.xml.parsers, + org.opendaylight.controller.config.persist.api, + org.opendaylight.controller.config.stat, + org.opendaylight.controller.config.persist.api.storage, + org.opendaylight.controller.netconf.api, + org.opendaylight.controller.netconf.api.jmx, + org.opendaylight.controller.netconf.client, + org.opendaylight.controller.netconf.util.osgi, + org.opendaylight.controller.netconf.util.xml, + org.osgi.framework, + org.slf4j, + org.w3c.dom, + org.xml.sax, + + + + + + + + + diff --git a/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/ConfigPersisterNotificationHandler.java b/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/ConfigPersisterNotificationHandler.java new file mode 100644 index 0000000000..c01a225a33 --- /dev/null +++ b/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/ConfigPersisterNotificationHandler.java @@ -0,0 +1,317 @@ +/* + * 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.netconf.persist.impl; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.Sets; +import org.opendaylight.controller.config.persist.api.Persister; +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.api.jmx.CommitJMXNotification; +import org.opendaylight.controller.netconf.api.jmx.DefaultCommitOperationMXBean; +import org.opendaylight.controller.netconf.api.jmx.NetconfJMXNotification; +import org.opendaylight.controller.netconf.client.NetconfClient; +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.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +import javax.annotation.concurrent.ThreadSafe; +import javax.management.*; +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.util.Collections; +import java.util.Set; + +/** + * Responsible for listening for notifications from netconf containing latest + * committed configuration that should be persisted, and also for loading last + * configuration. + */ +@ThreadSafe +public class ConfigPersisterNotificationHandler implements NotificationListener, Closeable { + + private static final Logger logger = LoggerFactory.getLogger(ConfigPersisterNotificationHandler.class); + + private final InetSocketAddress address; + + private NetconfClient netconfClient; + + private final Persister persister; + private final MBeanServerConnection mbeanServer; + private Long currentSessionId; + + private final ObjectName on = DefaultCommitOperationMXBean.objectName; + + public static final long DEFAULT_TIMEOUT = 40000L; + private final long timeout; + + public ConfigPersisterNotificationHandler(Persister persister, InetSocketAddress address, + MBeanServerConnection mbeanServer) { + this(persister, address, mbeanServer, DEFAULT_TIMEOUT); + } + + public ConfigPersisterNotificationHandler(Persister persister, InetSocketAddress address, + MBeanServerConnection mbeanServer, long timeout) { + this.persister = persister; + this.address = address; + this.mbeanServer = mbeanServer; + this.timeout = timeout; + } + + public void init() throws InterruptedException { + Optional maybeConfig = loadLastConfig(); + + if (maybeConfig.isPresent()) { + logger.debug("Last config found {}", persister); + + registerToNetconf(maybeConfig.get().getCapabilities()); + + final String configSnapshot = maybeConfig.get().getConfigSnapshot(); + try { + pushLastConfig(XmlUtil.readXmlToElement(configSnapshot)); + } catch (SAXException | IOException e) { + throw new IllegalStateException("Unable to load last config", e); + } + + } else { + // this ensures that netconf is initialized, this is first + // connection + // this means we can register as listener for commit + registerToNetconf(Collections.emptySet()); + + logger.info("No last config provided by backend storage {}", persister); + } + registerAsJMXListener(); + } + + private synchronized long registerToNetconf(Set expectedCaps) throws InterruptedException { + + Set currentCapabilities = Sets.newHashSet(); + + // TODO think about moving capability subset check to netconf client + // could be utilized by integration tests + + long pollingStart = System.currentTimeMillis(); + int delay = 5000; + + int attempt = 0; + + while (true) { + attempt++; + + try { + netconfClient = new NetconfClient(this.toString(), address, delay); + // TODO is this correct ex to catch ? + } catch (IllegalStateException e) { + logger.debug("Netconf {} was not initialized or is not stable, attempt {}", address, attempt, e); + Thread.sleep(delay); + continue; + } + currentCapabilities = netconfClient.getCapabilities(); + + if (isSubset(currentCapabilities, expectedCaps)) { + logger.debug("Hello from netconf stable with {} capabilities", currentCapabilities); + currentSessionId = netconfClient.getSessionId(); + logger.info("Session id received from netconf server: {}", currentSessionId); + return currentSessionId; + } + + if (System.currentTimeMillis() > pollingStart + timeout) { + break; + } + + logger.debug("Polling hello from netconf, attempt {}, capabilities {}", attempt, currentCapabilities); + + try { + netconfClient.close(); + } catch (IOException e) { + throw new RuntimeException("Error closing temporary client " + netconfClient); + } + + Thread.sleep(delay); + } + + throw new RuntimeException("Netconf server did not provide required capabilities " + expectedCaps + + " in time, provided capabilities " + currentCapabilities); + + } + + private boolean isSubset(Set currentCapabilities, Set expectedCaps) { + for (String exCap : expectedCaps) { + if (currentCapabilities.contains(exCap) == false) + return false; + } + return true; + } + + private void registerAsJMXListener() { + try { + mbeanServer.addNotificationListener(on, this, null, null); + } catch (InstanceNotFoundException | IOException e) { + throw new RuntimeException("Cannot register as JMX listener to netconf", e); + } + } + + @Override + public void handleNotification(Notification notification, Object handback) { + if (notification instanceof NetconfJMXNotification == false) + return; + + // Socket should not be closed at this point + // Activator unregisters this as JMX listener before close is called + + logger.debug("Received notification {}", notification); + if (notification instanceof CommitJMXNotification) { + try { + handleAfterCommitNotification((CommitJMXNotification) notification); + } catch (Exception e) { + // TODO: notificationBroadcast support logs only DEBUG + logger.warn("Exception occured during notification handling: ", e); + throw e; + } + } else + throw new IllegalStateException("Unknown config registry notification type " + notification); + } + + private void handleAfterCommitNotification(final CommitJMXNotification notification) { + try { + final XmlElement configElement = XmlElement.fromDomElement(notification.getConfigSnapshot()); + persister.persistConfig(new Persister.ConfigSnapshotHolder() { + @Override + public String getConfigSnapshot() { + return XmlUtil.toString(configElement.getDomElement()); + } + + @Override + public Set getCapabilities() { + return notification.getCapabilities(); + } + }); + logger.debug("Configuration persisted successfully"); + } catch (IOException e) { + throw new RuntimeException("Unable to persist configuration snapshot", e); + } + } + + private Optional loadLastConfig() { + Optional maybeConfigElement; + try { + maybeConfigElement = persister.loadLastConfig(); + } catch (IOException e) { + throw new RuntimeException("Unable to load configuration", e); + } + return maybeConfigElement; + } + + private synchronized void pushLastConfig(Element persistedConfig) { + StringBuilder response = new StringBuilder("editConfig response = {"); + + Element configElement = persistedConfig; + NetconfMessage message = createEditConfigMessage(configElement, "/netconfOp/editConfig.xml"); + NetconfMessage responseMessage = netconfClient.sendMessage(message); + + XmlElement element = XmlElement.fromDomDocument(responseMessage.getDocument()); + Preconditions.checkState(element.getName().equals(XmlNetconfConstants.RPC_REPLY_KEY)); + element = element.getOnlyChildElement(); + + checkIsOk(element, responseMessage); + response.append(XmlUtil.toString(responseMessage.getDocument())); + response.append("}"); + responseMessage = netconfClient.sendMessage(getNetconfMessageFromResource("/netconfOp/commit.xml")); + + element = XmlElement.fromDomDocument(responseMessage.getDocument()); + Preconditions.checkState(element.getName().equals(XmlNetconfConstants.RPC_REPLY_KEY)); + element = element.getOnlyChildElement(); + + checkIsOk(element, responseMessage); + response.append("commit response = {"); + response.append(XmlUtil.toString(responseMessage.getDocument())); + response.append("}"); + logger.debug("Last configuration loaded successfully"); + } + + private void checkIsOk(XmlElement element, NetconfMessage responseMessage) { + if (element.getName().equals(XmlNetconfConstants.OK)) { + return; + } else { + if (element.getName().equals(XmlNetconfConstants.RPC_ERROR)) { + logger.warn("Can not load last configuration, operation failed"); + throw new IllegalStateException("Can not load last configuration, operation failed: " + + XmlUtil.toString(responseMessage.getDocument())); + } + logger.warn("Can not load last configuration. Operation failed."); + throw new IllegalStateException("Can not load last configuration. Operation failed: " + + XmlUtil.toString(responseMessage.getDocument())); + } + } + + private NetconfMessage createEditConfigMessage(Element dataElement, String editConfigResourcename) { + try (InputStream stream = getClass().getResourceAsStream(editConfigResourcename)) { + Preconditions.checkNotNull(stream, "Unable to load resource " + editConfigResourcename); + + Document doc = XmlUtil.readXmlToDocument(stream); + + doc.getDocumentElement(); + XmlElement editConfigElement = XmlElement.fromDomDocument(doc).getOnlyChildElement(); + XmlElement configWrapper = editConfigElement.getOnlyChildElement(XmlNetconfConstants.CONFIG_KEY); + editConfigElement.getDomElement().removeChild(configWrapper.getDomElement()); + for (XmlElement el : XmlElement.fromDomElement(dataElement).getChildElements()) { + configWrapper.appendChild((Element) doc.importNode(el.getDomElement(), true)); + } + editConfigElement.appendChild(configWrapper.getDomElement()); + return new NetconfMessage(doc); + } catch (IOException | SAXException e) { + throw new RuntimeException("Unable to parse message from resources " + editConfigResourcename, e); + } + } + + private NetconfMessage getNetconfMessageFromResource(String resource) { + try (InputStream stream = getClass().getResourceAsStream(resource)) { + Preconditions.checkNotNull(stream, "Unable to load resource " + resource); + return new NetconfMessage(XmlUtil.readXmlToDocument(stream)); + } catch (SAXException | IOException e) { + throw new RuntimeException("Unable to parse message from resources " + resource, e); + } + } + + @Override + public synchronized void close() { + // TODO persister is received from constructor, should not be closed + // here + try { + persister.close(); + } catch (Exception e) { + logger.warn("Unable to close config persister {}", persister, e); + } + + if (netconfClient != null) { + try { + netconfClient.close(); + } catch (Exception e) { + logger.warn("Unable to close connection to netconf {}", netconfClient, e); + } + } + + // unregister from JMX + try { + if (mbeanServer.isRegistered(on)) { + mbeanServer.removeNotificationListener(on, this); + } + } catch (Exception e) { + logger.warn("Unable to unregister {} as listener for {}", this, on, e); + } + } +} diff --git a/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/NoOpStorageAdapter.java b/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/NoOpStorageAdapter.java new file mode 100644 index 0000000000..08b0d1a511 --- /dev/null +++ b/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/NoOpStorageAdapter.java @@ -0,0 +1,42 @@ +/* + * 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.netconf.persist.impl; + +import com.google.common.base.Optional; +import org.opendaylight.controller.config.persist.api.storage.StorageAdapter; +import org.opendaylight.controller.config.stat.ConfigProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +public class NoOpStorageAdapter implements StorageAdapter { + private static final Logger logger = LoggerFactory.getLogger(NoOpStorageAdapter.class); + + @Override + public void setProperties(ConfigProvider configProvider) { + logger.debug("setProperties called with {}", configProvider); + } + + @Override + public void persistConfig(ConfigSnapshotHolder holder) throws IOException { + logger.debug("persistConfig called with {}", holder); + } + + @Override + public Optional loadLastConfig() throws IOException { + logger.debug("loadLastConfig called"); + return Optional.absent(); + } + + @Override + public void close() throws IOException { + logger.debug("close called"); + } +} diff --git a/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/PersisterImpl.java b/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/PersisterImpl.java new file mode 100644 index 0000000000..03892f0da7 --- /dev/null +++ b/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/PersisterImpl.java @@ -0,0 +1,96 @@ +/* + * 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.netconf.persist.impl; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Optional; +import org.opendaylight.controller.config.persist.api.Persister; +import org.opendaylight.controller.config.persist.api.storage.StorageAdapter; +import org.opendaylight.controller.config.stat.ConfigProvider; + +import java.io.IOException; + +/** + * {@link Persister} implementation that delegates persisting functionality to + * underlying {@link Persister} called Storage Adapter. + * + * Storage adapters are low level persisters that do the heavy lifting for this + * class. Instances of storage adapters can be injected directly via constructor + * or instantiated from a full name of its class provided in a properties file. + * + * Name of storage adapter class should be located under + * {@link #STORAGE_ADAPTER_CLASS_PROP} key. + */ +public final class PersisterImpl implements Persister { + + public static final String STORAGE_ADAPTER_CLASS_PROP = "netconf.config.persister.storageAdapterClass"; + private final StorageAdapter storage; + + public PersisterImpl(StorageAdapter storage) { + this.storage = storage; + } + + public static Optional createFromProperties(ConfigProvider configProvider) { + String storageAdapterClass = configProvider.getProperty(STORAGE_ADAPTER_CLASS_PROP); + StorageAdapter storage; + if (storageAdapterClass == null || storageAdapterClass.equals("")) { + return Optional.absent(); + } + + try { + storage = StorageAdapter.class.cast(resolveClass(storageAdapterClass, StorageAdapter.class).newInstance()); + storage.setProperties(configProvider); + + } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { + throw new IllegalArgumentException("Unable to instantiate storage adapter from " + storageAdapterClass, e); + } + return Optional.of(new PersisterImpl(storage)); + } + + private static Class resolveClass(String storageAdapterClass, Class baseType) throws ClassNotFoundException { + Class clazz = Class.forName(storageAdapterClass); + + if (!isImplemented(baseType, clazz)) + throw new IllegalArgumentException("Storage adapter " + clazz + " has to implement " + baseType); + return clazz; + } + + private static boolean isImplemented(Class expectedIface, Class byClazz) { + for (Class iface : byClazz.getInterfaces()) { + if (iface.equals(expectedIface)) + return true; + } + return false; + } + + @Override + public void persistConfig(ConfigSnapshotHolder holder) throws IOException { + storage.persistConfig(holder); + } + + @Override + public Optional loadLastConfig() throws IOException { + return storage.loadLastConfig(); + } + + @VisibleForTesting + StorageAdapter getStorage() { + return storage; + } + + @Override + public void close() throws IOException { + storage.close(); + } + + @Override + public String toString() { + return "PersisterImpl [storage=" + storage + "]"; + } +} diff --git a/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/Util.java b/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/Util.java new file mode 100644 index 0000000000..b17309123c --- /dev/null +++ b/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/Util.java @@ -0,0 +1,31 @@ +/* + * 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.netconf.persist.impl; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; + +public final class Util { + + public static ScheduledExecutorService getExecutorServiceWithThreadName(final String threadNamePrefix, + int threadCount) { + return Executors.newScheduledThreadPool(threadCount, new ThreadFactory() { + + private int i = 1; + + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + thread.setName(threadNamePrefix + ":" + i++); + return thread; + } + }); + } +} diff --git a/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/osgi/ConfigPersisterActivator.java b/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/osgi/ConfigPersisterActivator.java new file mode 100644 index 0000000000..cf1b0af454 --- /dev/null +++ b/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/osgi/ConfigPersisterActivator.java @@ -0,0 +1,83 @@ +/* + * 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.netconf.persist.impl.osgi; + +import com.google.common.base.Optional; +import org.opendaylight.controller.netconf.persist.impl.ConfigPersisterNotificationHandler; +import org.opendaylight.controller.netconf.persist.impl.NoOpStorageAdapter; +import org.opendaylight.controller.netconf.persist.impl.PersisterImpl; +import org.opendaylight.controller.config.stat.ConfigProvider; +import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil; +import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil.TLSConfiguration; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.management.MBeanServer; +import java.lang.management.ManagementFactory; +import java.net.InetSocketAddress; + +public class ConfigPersisterActivator implements BundleActivator { + + private static final Logger logger = LoggerFactory.getLogger(ConfigPersisterActivator.class); + + private final static MBeanServer platformMBeanServer = ManagementFactory.getPlatformMBeanServer(); + + private ConfigPersisterNotificationHandler configPersisterNotificationHandler; + + private Thread initializationThread; + + @Override + public void start(BundleContext context) throws Exception { + logger.debug("ConfigPersister activator started"); + + ConfigProvider configProvider = new ConfigProvider.ConfigProviderImpl(context); + Optional maybePersister = PersisterImpl.createFromProperties(configProvider); + if (maybePersister.isPresent() == false) { + throw new IllegalStateException("No persister is defined in " + PersisterImpl.STORAGE_ADAPTER_CLASS_PROP + + " property. For noop persister use " + NoOpStorageAdapter.class.getCanonicalName() + + " . Persister is not operational"); + } + + Optional maybeTLSConfiguration = NetconfConfigUtil.extractTLSConfiguration(configProvider); + Optional maybeTCPAddress = NetconfConfigUtil.extractTCPNetconfAddress(configProvider); + + InetSocketAddress address; + if (maybeTLSConfiguration.isPresent()) { + throw new UnsupportedOperationException("TLS is currently not supported for persister"); + } else if (maybeTCPAddress.isPresent()) { + address = maybeTCPAddress.get(); + } else { + throw new IllegalStateException("Netconf is not configured, persister is not operational"); + } + + PersisterImpl persister = maybePersister.get(); + configPersisterNotificationHandler = new ConfigPersisterNotificationHandler(persister, address, + platformMBeanServer); + Runnable initializationRunnable = new Runnable() { + @Override + public void run() { + try { + configPersisterNotificationHandler.init(); + } catch (InterruptedException e) { + logger.info("Interrupted while waiting for netconf connection"); + } + } + }; + initializationThread = new Thread(initializationRunnable, "ConfigPersister-registrator"); + initializationThread.start(); + } + + @Override + public void stop(BundleContext context) throws Exception { + initializationThread.interrupt(); + configPersisterNotificationHandler.close(); + } +} diff --git a/opendaylight/netconf/config-persister-impl/src/main/resources/netconfOp/client_hello.xml b/opendaylight/netconf/config-persister-impl/src/main/resources/netconfOp/client_hello.xml new file mode 100644 index 0000000000..4e34591284 --- /dev/null +++ b/opendaylight/netconf/config-persister-impl/src/main/resources/netconfOp/client_hello.xml @@ -0,0 +1,5 @@ + + + urn:ietf:params:netconf:base:1.0 + + diff --git a/opendaylight/netconf/config-persister-impl/src/main/resources/netconfOp/commit.xml b/opendaylight/netconf/config-persister-impl/src/main/resources/netconfOp/commit.xml new file mode 100644 index 0000000000..ae1f6e87fa --- /dev/null +++ b/opendaylight/netconf/config-persister-impl/src/main/resources/netconfOp/commit.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/opendaylight/netconf/config-persister-impl/src/main/resources/netconfOp/editConfig.xml b/opendaylight/netconf/config-persister-impl/src/main/resources/netconfOp/editConfig.xml new file mode 100644 index 0000000000..8ec1c8c069 --- /dev/null +++ b/opendaylight/netconf/config-persister-impl/src/main/resources/netconfOp/editConfig.xml @@ -0,0 +1,12 @@ + + + + + + replace + + + + + + \ No newline at end of file diff --git a/opendaylight/netconf/config-persister-impl/src/test/java/org/opendaylight/controller/netconf/persist/impl/PersisterImplTest.java b/opendaylight/netconf/config-persister-impl/src/test/java/org/opendaylight/controller/netconf/persist/impl/PersisterImplTest.java new file mode 100644 index 0000000000..36e88251dd --- /dev/null +++ b/opendaylight/netconf/config-persister-impl/src/test/java/org/opendaylight/controller/netconf/persist/impl/PersisterImplTest.java @@ -0,0 +1,132 @@ +/* + * 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.netconf.persist.impl; + +import com.google.common.base.Optional; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.opendaylight.controller.config.persist.api.Persister; +import org.opendaylight.controller.config.persist.api.storage.StorageAdapter; +import org.opendaylight.controller.config.persist.storage.file.FileStorageAdapter; +import org.opendaylight.controller.config.stat.ConfigProvider; + +import java.io.File; +import java.io.IOException; + +import static org.junit.Assert.*; +import static org.junit.matchers.JUnitMatchers.containsString; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.*; + +public class PersisterImplTest { + @Mock + ConfigProvider mockedConfigProvider; + + @Before + public void setUpMocks() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testFromProperties() throws Exception { + doReturn(MockAdapter.class.getName()).when(mockedConfigProvider).getProperty( + PersisterImpl.STORAGE_ADAPTER_CLASS_PROP); + + PersisterImpl persisterImpl = PersisterImpl.createFromProperties(mockedConfigProvider).get(); + persisterImpl.persistConfig(null); + persisterImpl.loadLastConfig(); + persisterImpl.persistConfig(null); + persisterImpl.loadLastConfig(); + + assertEquals(2, MockAdapter.persist); + assertEquals(2, MockAdapter.load); + assertEquals(1, MockAdapter.props); + } + + @Test + public void testFromProperties2() throws Exception { + mockedConfigProvider = mock(ConfigProvider.class); + doReturn(FileStorageAdapter.class.getName()).when(mockedConfigProvider).getProperty( + PersisterImpl.STORAGE_ADAPTER_CLASS_PROP); + doReturn("target" + File.separator + "generated-test-sources" + File.separator + "testFile").when( + mockedConfigProvider).getProperty(FileStorageAdapter.FILE_STORAGE_PROP); + doReturn("mockedConfigProvider").when(mockedConfigProvider).toString(); + doReturn(null).when(mockedConfigProvider).getProperty("numberOfBackups"); + + PersisterImpl persisterImpl = PersisterImpl.createFromProperties(mockedConfigProvider).get(); + assertTrue(persisterImpl.getStorage() instanceof FileStorageAdapter); + } + + @Test + public void testFromProperties3() throws Exception { + mockedConfigProvider = mock(ConfigProvider.class); + doReturn(FileStorageAdapter.class.getName()).when(mockedConfigProvider).getProperty( + PersisterImpl.STORAGE_ADAPTER_CLASS_PROP); + doReturn("target" + File.separator + "generated-test-sources" + File.separator + "testFile").when( + mockedConfigProvider).getProperty(FileStorageAdapter.FILE_STORAGE_PROP); + doReturn("mockedConfigProvider").when(mockedConfigProvider).toString(); + doReturn("0").when(mockedConfigProvider).getProperty("numberOfBackups"); + try { + PersisterImpl.createFromProperties(mockedConfigProvider).get(); + fail(); + } catch (RuntimeException e) { + assertThat( + e.getMessage(), + containsString("numberOfBackups property should be either set to positive value, or ommited. Can not be set to 0.")); + } + } + + @Test + public void test() throws Exception { + StorageAdapter storage = mock(StorageAdapter.class); + doReturn(null).when(storage).loadLastConfig(); + doNothing().when(storage).persistConfig(any(Persister.ConfigSnapshotHolder.class)); + PersisterImpl persister = new PersisterImpl(storage); + persister.loadLastConfig(); + persister.persistConfig(null); + + verify(storage).loadLastConfig(); + verify(storage).persistConfig(any(Persister.ConfigSnapshotHolder.class)); + } + + public static class MockAdapter implements StorageAdapter { + + static int persist = 0; + + @Override + public void persistConfig(ConfigSnapshotHolder holder) throws IOException { + persist++; + } + + static int load = 0; + + @Override + public Optional loadLastConfig() throws IOException { + load++; + return null;// ? + } + + static int props = 0; + + @Override + public void setProperties(ConfigProvider configProvider) { + props++; + } + + @Override + public void close() throws IOException { + // TODO Auto-generated method stub + + } + + } + +} diff --git a/opendaylight/netconf/netconf-api/pom.xml b/opendaylight/netconf/netconf-api/pom.xml new file mode 100644 index 0000000000..57e77cc945 --- /dev/null +++ b/opendaylight/netconf/netconf-api/pom.xml @@ -0,0 +1,54 @@ + + + + + netconf-subsystem + org.opendaylight.controller + 0.2.1-SNAPSHOT + + 4.0.0 + netconf-api + ${project.artifactId} + bundle + + + + org.opendaylight.controller + config-api + 0.2.1-SNAPSHOT + + + org.opendaylight.bgpcep + framework + + + + + + + org.apache.felix + maven-bundle-plugin + + + + + + javax.management, + org.opendaylight.controller.config.api.jmx, + org.opendaylight.protocol.framework, + org.w3c.dom + + + org.opendaylight.controller.netconf.api, + org.opendaylight.controller.netconf.api.jmx, + + + + + + + + + diff --git a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfDeserializerException.java b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfDeserializerException.java new file mode 100644 index 0000000000..f42db6938d --- /dev/null +++ b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfDeserializerException.java @@ -0,0 +1,28 @@ +/* + * 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.netconf.api; + +import org.opendaylight.protocol.framework.DeserializerException; + +/** + * This exception is thrown by + * {@link NetconfSessionListener#onMessage(NetconfMessage)} to indicate fatal + * communication problem after which the session should be closed. + */ +public class NetconfDeserializerException extends DeserializerException { + private static final long serialVersionUID = 1L; + + public NetconfDeserializerException(final String message) { + super(message); + } + + public NetconfDeserializerException(final String message, final Exception e) { + super(message, e); + } +} diff --git a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfDocumentedException.java b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfDocumentedException.java new file mode 100644 index 0000000000..1107572df1 --- /dev/null +++ b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfDocumentedException.java @@ -0,0 +1,111 @@ +/* + * 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.netconf.api; + +import java.util.Collections; +import java.util.Map; + +import org.opendaylight.protocol.framework.DocumentedException; + +/** + * Checked exception to communicate an error that needs to be sent to the + * netconf client. + */ +public class NetconfDocumentedException extends DocumentedException { + + private static final long serialVersionUID = 1L; + + public enum ErrorType { + transport, rpc, protocol, application; + + public String getTagValue() { + return name(); + } + } + + public enum ErrorTag { + missing_attribute("missing-attribute"), unknown_element("unknown-element"), operation_not_supported( + "operation-not-supported"), bad_attribute("bad-attribute"), data_missing("data-missing"), operation_failed( + "operation-failed"), invalid_value("invalid-value"), malformed_message("malformed-message"); + + private final String tagValue; + + ErrorTag(final String tagValue) { + this.tagValue = tagValue; + } + + public String getTagValue() { + return this.tagValue; + } + } + + public enum ErrorSeverity { + error, warning; + + public String getTagValue() { + return name(); + } + } + + private final ErrorType errorType; + private final ErrorTag errorTag; + private final ErrorSeverity errorSeverity; + private final Map errorInfo; + + public NetconfDocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag, + final ErrorSeverity errorSeverity) { + this(message, errorType, errorTag, errorSeverity, Collections. emptyMap()); + } + + public NetconfDocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag, + final ErrorSeverity errorSeverity, final Map errorInfo) { + super(message); + this.errorType = errorType; + this.errorTag = errorTag; + this.errorSeverity = errorSeverity; + this.errorInfo = errorInfo; + } + + public NetconfDocumentedException(final String message, final Exception cause, final ErrorType errorType, + final ErrorTag errorTag, final ErrorSeverity errorSeverity) { + this(message, cause, errorType, errorTag, errorSeverity, Collections. emptyMap()); + } + + public NetconfDocumentedException(final String message, final Exception cause, final ErrorType errorType, + final ErrorTag errorTag, final ErrorSeverity errorSeverity, final Map errorInfo) { + super(message, cause); + this.errorType = errorType; + this.errorTag = errorTag; + this.errorSeverity = errorSeverity; + this.errorInfo = errorInfo; + } + + public ErrorType getErrorType() { + return this.errorType; + } + + public ErrorTag getErrorTag() { + return this.errorTag; + } + + public ErrorSeverity getErrorSeverity() { + return this.errorSeverity; + } + + public Map getErrorInfo() { + return this.errorInfo; + } + + @Override + public String toString() { + return "NetconfDocumentedException{" + "message=" + getMessage() + ", errorType=" + this.errorType + + ", errorTag=" + this.errorTag + ", errorSeverity=" + this.errorSeverity + ", errorInfo=" + + this.errorInfo + '}'; + } +} diff --git a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfMessage.java b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfMessage.java new file mode 100644 index 0000000000..33d41b0470 --- /dev/null +++ b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfMessage.java @@ -0,0 +1,31 @@ +/* + * 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.netconf.api; + +import org.opendaylight.protocol.framework.ProtocolMessage; +import org.w3c.dom.Document; + +/** + * NetconfMessage represents a wrapper around org.w3c.dom.Document. Needed for + * implementing ProtocolMessage interface. + */ +public final class NetconfMessage implements ProtocolMessage { + + private static final long serialVersionUID = 462175939836367285L; + + private final Document doc; + + public NetconfMessage(final Document doc) { + this.doc = doc; + } + + public Document getDocument() { + return this.doc; + } +} diff --git a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfOperationRouter.java b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfOperationRouter.java new file mode 100644 index 0000000000..49ca0c0106 --- /dev/null +++ b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfOperationRouter.java @@ -0,0 +1,20 @@ +/* + * 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.netconf.api; + +import org.w3c.dom.Document; + +public interface NetconfOperationRouter extends AutoCloseable { + + Document onNetconfMessage(Document message) throws NetconfDocumentedException; + + @Override + void close(); + +} diff --git a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfServerSessionPreferences.java b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfServerSessionPreferences.java new file mode 100644 index 0000000000..d56213cf16 --- /dev/null +++ b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfServerSessionPreferences.java @@ -0,0 +1,26 @@ +/* + * 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.netconf.api; + +/** + * The only input for the start of a NETCONF session is hello-message. + */ +public final class NetconfServerSessionPreferences extends NetconfSessionPreferences { + + private final long sessionId; + + public NetconfServerSessionPreferences(final NetconfMessage helloMessage, long sessionId) { + super(helloMessage); + this.sessionId = sessionId; + } + + public long getSessionId() { + return sessionId; + } +} diff --git a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfSession.java b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfSession.java new file mode 100644 index 0000000000..a61d6938f6 --- /dev/null +++ b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfSession.java @@ -0,0 +1,16 @@ +/* + * 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.netconf.api; + +import org.opendaylight.protocol.framework.AbstractProtocolSession; + +public abstract class NetconfSession extends AbstractProtocolSession { + + public abstract void sendMessage(NetconfMessage netconfMessage); +} diff --git a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfSessionListener.java b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfSessionListener.java new file mode 100644 index 0000000000..54cb471604 --- /dev/null +++ b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfSessionListener.java @@ -0,0 +1,16 @@ +/* + * 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.netconf.api; + +import org.opendaylight.protocol.framework.SessionListener; + +public interface NetconfSessionListener extends + SessionListener { + +} diff --git a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfSessionPreferences.java b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfSessionPreferences.java new file mode 100644 index 0000000000..1ec46c8d6f --- /dev/null +++ b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfSessionPreferences.java @@ -0,0 +1,24 @@ +/* + * 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.netconf.api; + +public class NetconfSessionPreferences { + protected final NetconfMessage helloMessage; + + public NetconfSessionPreferences(final NetconfMessage helloMessage) { + this.helloMessage = helloMessage; + } + + /** + * @return the helloMessage + */ + public NetconfMessage getHelloMessage() { + return this.helloMessage; + } +} diff --git a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfTerminationReason.java b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfTerminationReason.java new file mode 100644 index 0000000000..9de3071060 --- /dev/null +++ b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfTerminationReason.java @@ -0,0 +1,25 @@ +/* + * 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.netconf.api; + +import org.opendaylight.protocol.framework.TerminationReason; + +public class NetconfTerminationReason implements TerminationReason { + + private final String reason; + + public NetconfTerminationReason(String reason) { + this.reason = reason; + } + + @Override + public String getErrorMessage() { + return reason; + } +} diff --git a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/jmx/CommitJMXNotification.java b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/jmx/CommitJMXNotification.java new file mode 100644 index 0000000000..0c4174000f --- /dev/null +++ b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/jmx/CommitJMXNotification.java @@ -0,0 +1,52 @@ +/* + * 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.netconf.api.jmx; + +import org.w3c.dom.Element; + +import javax.management.NotificationBroadcasterSupport; +import java.util.Set; + +public class CommitJMXNotification extends NetconfJMXNotification { + + private final Element configSnapshot; + + private static final String afterCommitMessageTemplate = "Commit successful: %s"; + private final Set capabilities; + + CommitJMXNotification(NotificationBroadcasterSupport source, String message, Element cfgSnapshot, + Set capabilities) { + super(TransactionProviderJMXNotificationType.commit, source, String.format(afterCommitMessageTemplate, message)); + this.configSnapshot = cfgSnapshot; + this.capabilities = capabilities; + } + + public Element getConfigSnapshot() { + return configSnapshot; + } + + public Set getCapabilities() { + return capabilities; + } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("CommitJMXNotification{"); + sb.append("configSnapshot=").append(configSnapshot); + sb.append(", capabilities=").append(getCapabilities()); + sb.append('}'); + return sb.toString(); + } + + /** + * + */ + private static final long serialVersionUID = -8587623362011695514L; + +} diff --git a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/jmx/DefaultCommitOperationMXBean.java b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/jmx/DefaultCommitOperationMXBean.java new file mode 100644 index 0000000000..a170d2905f --- /dev/null +++ b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/jmx/DefaultCommitOperationMXBean.java @@ -0,0 +1,20 @@ +/* + * 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.netconf.api.jmx; + +import org.opendaylight.controller.config.api.jmx.ObjectNameUtil; + +import javax.management.ObjectName; + +public interface DefaultCommitOperationMXBean { + + static String typeName = "NetconfNotificationProvider"; + public static ObjectName objectName = ObjectNameUtil.createONWithDomainAndType(typeName); + +} diff --git a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/jmx/NetconfJMXNotification.java b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/jmx/NetconfJMXNotification.java new file mode 100644 index 0000000000..5aa4bffce3 --- /dev/null +++ b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/jmx/NetconfJMXNotification.java @@ -0,0 +1,64 @@ +/* + * 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.netconf.api.jmx; + +import java.util.Set; + +import javax.management.Notification; +import javax.management.NotificationBroadcasterSupport; + +import org.w3c.dom.Element; + +public abstract class NetconfJMXNotification extends Notification { + + /** + * + */ + private static final long serialVersionUID = 6754474563863772845L; + + private static long sequenceNumber = 1; + + private final TransactionProviderJMXNotificationType type; + + protected NetconfJMXNotification(TransactionProviderJMXNotificationType type, + NotificationBroadcasterSupport source, String message) { + super(type.toString(), source, sequenceNumber++, System.nanoTime(), message); + this.type = type; + } + + @Override + public String toString() { + return "TransactionProviderJMXNotification [type=" + type + "]"; + } + + /** + * Sends this notification using source that created it + */ + public void send() { + ((NotificationBroadcasterSupport) getSource()).sendNotification(this); + } + + /** + * Creates notification about successful commit execution. + * + * Intended for config-persister. + * + * @param transactionName + * @param cfgSnapshot + */ + public static CommitJMXNotification afterCommit(NotificationBroadcasterSupport source, String message, + Element cfgSnapshot, Set capabilities) { + return new CommitJMXNotification(source, message, cfgSnapshot, capabilities); + } + + static enum TransactionProviderJMXNotificationType { + commit; + } + +} diff --git a/opendaylight/netconf/netconf-client/pom.xml b/opendaylight/netconf/netconf-client/pom.xml new file mode 100644 index 0000000000..ac949f5445 --- /dev/null +++ b/opendaylight/netconf/netconf-client/pom.xml @@ -0,0 +1,77 @@ + + 4.0.0 + + + netconf-subsystem + org.opendaylight.controller + 0.2.1-SNAPSHOT + + netconf-client + ${project.artifactId} + bundle + + + + + ${project.groupId} + netconf-util + ${project.version} + + + ${project.groupId} + netconf-api + ${project.version} + + + org.opendaylight.bgpcep + framework + + + + com.google.guava + guava + + + org.slf4j + slf4j-api + + + + + + + org.apache.felix + maven-bundle-plugin + + + + org.opendaylight.controller.netconf.client, + + + com.google.common.base, + com.google.common.collect, + io.netty.channel, + io.netty.channel.socket, + io.netty.util, + io.netty.util.concurrent, + javax.annotation, + javax.net.ssl, + javax.xml.namespace, + javax.xml.parsers, + javax.xml.xpath, + org.opendaylight.controller.netconf.api, + org.opendaylight.controller.netconf.util, + org.opendaylight.controller.netconf.util.xml, + org.opendaylight.protocol.framework, + org.slf4j, + org.w3c.dom, + org.xml.sax + + + + + + + + diff --git a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClient.java b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClient.java new file mode 100644 index 0000000000..b5a06ca0ab --- /dev/null +++ b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClient.java @@ -0,0 +1,138 @@ +/* + * 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.netconf.client; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.Sets; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GlobalEventExecutor; +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.protocol.framework.NeverReconnectStrategy; +import org.opendaylight.protocol.framework.ReconnectStrategy; +import org.opendaylight.protocol.framework.TimedReconnectStrategy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.SSLContext; +import java.io.Closeable; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Set; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; + +public class NetconfClient implements Closeable { + + private static final Logger logger = LoggerFactory.getLogger(NetconfClient.class); + + public static final int DEFAULT_CONNECT_TIMEOUT = 5000; + private final NetconfClientDispatcher dispatch; + private final String label; + private final NetconfClientSession clientSession; + private final NetconfClientSessionListener sessionListener; + private final long sessionId; + private final InetSocketAddress address; + + // TODO test reconnecting constructor + public NetconfClient(String clientLabelForLogging, InetSocketAddress address, int connectionAttempts, + int attemptMsTimeout) { + this(clientLabelForLogging, address, getReconnectStrategy(connectionAttempts, attemptMsTimeout), Optional + . absent()); + } + + private NetconfClient(String clientLabelForLogging, InetSocketAddress address, ReconnectStrategy strat, + Optional maybeSSLContext) { + this.label = clientLabelForLogging; + dispatch = new NetconfClientDispatcher(maybeSSLContext); + + sessionListener = new NetconfClientSessionListener(); + Future clientFuture = dispatch.createClient(address, sessionListener, strat); + this.address = address; + clientSession = get(clientFuture); + this.sessionId = clientSession.getSessionId(); + } + + private NetconfClientSession get(Future clientFuture) { + try { + return clientFuture.get(); + } catch (InterruptedException | CancellationException e) { + throw new RuntimeException("Netconf client interrupted", e); + } catch (ExecutionException e) { + throw new IllegalStateException("Unable to create netconf client", e); + } + } + + public NetconfClient(String clientLabelForLogging, InetSocketAddress address, int connectTimeoutMs, + Optional maybeSSLContext) { + this(clientLabelForLogging, address, + new NeverReconnectStrategy(GlobalEventExecutor.INSTANCE, connectTimeoutMs), maybeSSLContext); + } + + public NetconfClient(String clientLabelForLogging, InetSocketAddress address, int connectTimeoutMs) { + this(clientLabelForLogging, address, + new NeverReconnectStrategy(GlobalEventExecutor.INSTANCE, connectTimeoutMs), Optional + . absent()); + } + + public NetconfClient(String clientLabelForLogging, InetSocketAddress address) { + this(clientLabelForLogging, address, new NeverReconnectStrategy(GlobalEventExecutor.INSTANCE, + DEFAULT_CONNECT_TIMEOUT), Optional. absent()); + } + + public NetconfClient(String clientLabelForLogging, InetSocketAddress address, Optional maybeSSLContext) { + this(clientLabelForLogging, address, new NeverReconnectStrategy(GlobalEventExecutor.INSTANCE, + DEFAULT_CONNECT_TIMEOUT), maybeSSLContext); + } + + public NetconfMessage sendMessage(NetconfMessage message) { + return sendMessage(message, 5, 1000); + } + + public NetconfMessage sendMessage(NetconfMessage message, int attempts, int attemptMsDelay) { + Preconditions.checkState(clientSession.isUp(), "Session was not up yet"); + clientSession.sendMessage(message); + try { + return sessionListener.getLastMessage(attempts, attemptMsDelay); + } catch (InterruptedException e) { + throw new RuntimeException(this + " Cannot read message from " + address, e); + } catch (IllegalStateException e) { + throw new IllegalStateException(this + " Cannot read message from " + address, e); + } + } + + @Override + public void close() throws IOException { + clientSession.close(); + dispatch.close(); + } + + private static ReconnectStrategy getReconnectStrategy(int connectionAttempts, int attemptMsTimeout) { + return new TimedReconnectStrategy(GlobalEventExecutor.INSTANCE, attemptMsTimeout, 1000, 1.0, null, + Long.valueOf(connectionAttempts), null); + } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("NetconfClient{"); + sb.append("label=").append(label); + sb.append(", sessionId=").append(sessionId); + sb.append('}'); + return sb.toString(); + } + + public long getSessionId() { + return sessionId; + } + + public Set getCapabilities() { + Preconditions.checkState(clientSession != null, "Client was not initialized successfully"); + return Sets.newHashSet(clientSession.getServerCapabilities()); + } +} diff --git a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientDispatcher.java b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientDispatcher.java new file mode 100644 index 0000000000..4df8235441 --- /dev/null +++ b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientDispatcher.java @@ -0,0 +1,84 @@ +/* + * 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.netconf.client; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import io.netty.channel.socket.SocketChannel; +import io.netty.util.HashedWheelTimer; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.Promise; +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.api.NetconfSession; +import org.opendaylight.controller.netconf.api.NetconfTerminationReason; +import org.opendaylight.controller.netconf.util.AbstractChannelInitializer; +import org.opendaylight.protocol.framework.AbstractDispatcher; +import org.opendaylight.protocol.framework.ReconnectStrategy; +import org.opendaylight.protocol.framework.SessionListener; +import org.opendaylight.protocol.framework.SessionListenerFactory; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import java.net.InetSocketAddress; + +public class NetconfClientDispatcher extends AbstractDispatcher { + + private final Optional maybeContext; + private final NetconfClientSessionNegotiatorFactory negotatorFactory; + + public NetconfClientDispatcher(final Optional maybeContext) { + this.maybeContext = Preconditions.checkNotNull(maybeContext); + this.negotatorFactory = new NetconfClientSessionNegotiatorFactory(new HashedWheelTimer()); + } + + public Future createClient(InetSocketAddress address, + final NetconfClientSessionListener sessionListener, ReconnectStrategy strat) { + + return super.createClient(address, strat, new PipelineInitializer() { + + @Override + public void initializeChannel(final SocketChannel ch, final Promise promise) { + initialize(ch, promise); + } + + private void initialize(SocketChannel ch, Promise promise) { + new ClientChannelInitializer(maybeContext, negotatorFactory, sessionListener).initialize(ch, promise); + } + }); + } + + private static class ClientChannelInitializer extends AbstractChannelInitializer { + + private final NetconfClientSessionNegotiatorFactory negotiatorFactory; + private final NetconfClientSessionListener sessionListener; + + private ClientChannelInitializer(Optional maybeContext, + NetconfClientSessionNegotiatorFactory negotiatorFactory, NetconfClientSessionListener sessionListener) { + super(maybeContext); + this.negotiatorFactory = negotiatorFactory; + this.sessionListener = sessionListener; + } + + @Override + protected void initializeAfterDecoder(SocketChannel ch, Promise promise) { + ch.pipeline().addLast("negotiator", negotiatorFactory.getSessionNegotiator(new SessionListenerFactory() { + @Override + public SessionListener getSessionListener() { + return sessionListener; + } + }, ch, promise)); + } + + @Override + protected void initSslEngine(SSLEngine sslEngine) { + sslEngine.setUseClientMode(true); + } + } + +} diff --git a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSession.java b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSession.java new file mode 100644 index 0000000000..f0180cf78d --- /dev/null +++ b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSession.java @@ -0,0 +1,95 @@ +/* + * 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.netconf.client; + +import io.netty.channel.Channel; +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.api.NetconfSession; +import org.opendaylight.controller.netconf.api.NetconfTerminationReason; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.opendaylight.protocol.framework.SessionListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Collection; + +public class NetconfClientSession extends NetconfSession { + + private final SessionListener sessionListener; + private final long sessionId; + private final Channel channel; + + private static final Logger logger = LoggerFactory.getLogger(NetconfClientSession.class); + private final Collection capabilities; + private boolean up; + + public NetconfClientSession(SessionListener sessionListener, Channel channel, long sessionId, + Collection capabilities) { + this.sessionListener = sessionListener; + this.channel = channel; + this.sessionId = sessionId; + this.capabilities = capabilities; + logger.debug("Client Session {} created", toString()); + } + + @Override + public void close() { + channel.close(); + sessionListener.onSessionTerminated(this, new NetconfTerminationReason("Client Session closed")); + } + + @Override + protected void handleMessage(NetconfMessage netconfMessage) { + logger.debug("Client Session {} received message {}", toString(), + XmlUtil.toString(netconfMessage.getDocument())); + sessionListener.onMessage(this, netconfMessage); + } + + @Override + public void sendMessage(NetconfMessage netconfMessage) { + channel.writeAndFlush(netconfMessage); + } + + @Override + protected void endOfInput() { + logger.debug("Client Session {} end of input detected while session was in state {}", toString(), isUp() ? "up" + : "initialized"); + if (isUp()) { + this.sessionListener.onSessionDown(this, new IOException("End of input detected. Close the session.")); + } + } + + @Override + protected void sessionUp() { + logger.debug("Client Session {} up", toString()); + sessionListener.onSessionUp(this); + this.up = true; + } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("ClientNetconfSession{"); + sb.append("sessionId=").append(sessionId); + sb.append('}'); + return sb.toString(); + } + + public boolean isUp() { + return up; + } + + public long getSessionId() { + return sessionId; + } + + public Collection getServerCapabilities() { + return capabilities; + } +} diff --git a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSessionListener.java b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSessionListener.java new file mode 100644 index 0000000000..d3c1b22c84 --- /dev/null +++ b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSessionListener.java @@ -0,0 +1,75 @@ +/* + * 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.netconf.client; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.api.NetconfTerminationReason; +import org.opendaylight.protocol.framework.SessionListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +public class NetconfClientSessionListener implements + SessionListener { + + private static final Logger logger = LoggerFactory.getLogger(NetconfClientSessionListener.class); + private AtomicBoolean up = new AtomicBoolean(false); + + @Override + public void onSessionUp(NetconfClientSession clientSession) { + up.set(true); + } + + @Override + public void onSessionDown(NetconfClientSession clientSession, Exception e) { + logger.debug("Client Session {} down, reason: {}", clientSession, e.getMessage()); + up.set(false); + } + + @Override + public void onSessionTerminated(NetconfClientSession clientSession, + NetconfTerminationReason netconfTerminationReason) { + logger.debug("Client Session {} terminated, reason: {}", clientSession, + netconfTerminationReason.getErrorMessage()); + up.set(false); + } + + @Override + public synchronized void onMessage(NetconfClientSession session, NetconfMessage message) { + synchronized (messages) { + this.messages.add(message); + } + } + + private int lastReadMessage = -1; + private List messages = Lists.newArrayList(); + + public NetconfMessage getLastMessage(int attempts, int attemptMsDelay) throws InterruptedException { + Preconditions.checkState(up.get(), "Session was not up yet"); + + for (int i = 0; i < attempts; i++) { + synchronized (messages) { + if (messages.size() - 1 > lastReadMessage) { + lastReadMessage++; + return messages.get(lastReadMessage); + } + } + + if (up.get() == false) + throw new IllegalStateException("Session ended while trying to read message"); + Thread.sleep(attemptMsDelay); + } + + throw new IllegalStateException("No netconf message to read"); + } +} diff --git a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSessionNegotiator.java b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSessionNegotiator.java new file mode 100644 index 0000000000..17a55c52bc --- /dev/null +++ b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSessionNegotiator.java @@ -0,0 +1,72 @@ +/* + * 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.netconf.client; + +import com.google.common.base.Function; +import com.google.common.collect.Collections2; +import io.netty.channel.Channel; +import io.netty.util.Timer; +import io.netty.util.concurrent.Promise; +import org.opendaylight.controller.netconf.api.NetconfSessionPreferences; +import org.opendaylight.controller.netconf.util.AbstractNetconfSessionNegotiator; +import org.opendaylight.controller.netconf.util.xml.XMLNetconfUtil; +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.protocol.framework.SessionListener; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +import javax.annotation.Nullable; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import java.util.Collection; +import java.util.List; + +public class NetconfClientSessionNegotiator extends + AbstractNetconfSessionNegotiator { + + protected NetconfClientSessionNegotiator(NetconfSessionPreferences sessionPreferences, + Promise promise, Channel channel, Timer timer, SessionListener sessionListener) { + super(sessionPreferences, promise, channel, timer, sessionListener); + } + + private static Collection getCapabilities(Document doc) { + XmlElement responseElement = XmlElement.fromDomDocument(doc); + XmlElement capabilitiesElement = responseElement + .getOnlyChildElementWithSameNamespace(XmlNetconfConstants.CAPABILITIES); + List caps = capabilitiesElement.getChildElements(XmlNetconfConstants.CAPABILITY); + return Collections2.transform(caps, new Function() { + + @Nullable + @Override + public String apply(@Nullable XmlElement input) { + return input.getTextContent(); + } + }); + } + + private static final XPathExpression sessionIdXPath = XMLNetconfUtil + .compileXPath("/netconf:hello/netconf:session-id"); + + private long extractSessionId(Document doc) { + final Node sessionIdNode = (Node) XmlUtil.evaluateXPath(sessionIdXPath, doc, XPathConstants.NODE); + String textContent = sessionIdNode.getTextContent(); + if (textContent == null || textContent.equals("")) { + throw new IllegalStateException("Session id not received from server"); + } + + return Long.valueOf(textContent); + } + + @Override + protected NetconfClientSession getSession(SessionListener sessionListener, Channel channel, Document doc) { + return new NetconfClientSession(sessionListener, channel, extractSessionId(doc), getCapabilities(doc)); + } +} diff --git a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSessionNegotiatorFactory.java b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSessionNegotiatorFactory.java new file mode 100644 index 0000000000..db0b953bdd --- /dev/null +++ b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfClientSessionNegotiatorFactory.java @@ -0,0 +1,53 @@ +/* + * 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.netconf.client; + +import com.google.common.base.Preconditions; +import io.netty.channel.Channel; +import io.netty.util.Timer; +import io.netty.util.concurrent.Promise; +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.api.NetconfSessionPreferences; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.opendaylight.protocol.framework.SessionListenerFactory; +import org.opendaylight.protocol.framework.SessionNegotiator; +import org.opendaylight.protocol.framework.SessionNegotiatorFactory; +import org.xml.sax.SAXException; + +import java.io.IOException; +import java.io.InputStream; + +public class NetconfClientSessionNegotiatorFactory implements SessionNegotiatorFactory { + + private final Timer timer; + + public NetconfClientSessionNegotiatorFactory(Timer timer) { + this.timer = timer; + } + + private static NetconfMessage loadHelloMessageTemplate() { + final String helloMessagePath = "/client_hello.xml"; + try (InputStream is = NetconfClientSessionNegotiatorFactory.class.getResourceAsStream(helloMessagePath)) { + Preconditions.checkState(is != null, "Input stream from %s was null", helloMessagePath); + return new NetconfMessage(XmlUtil.readXmlToDocument(is)); + } catch (SAXException | IOException e) { + throw new RuntimeException("Unable to load hello message", e); + } + } + + @Override + public SessionNegotiator getSessionNegotiator(SessionListenerFactory sessionListenerFactory, Channel channel, + Promise promise) { + // Hello message needs to be recreated every time + NetconfSessionPreferences proposal = new NetconfSessionPreferences(loadHelloMessageTemplate()); + return new NetconfClientSessionNegotiator(proposal, promise, channel, timer, + sessionListenerFactory.getSessionListener()); + } + +} diff --git a/opendaylight/netconf/netconf-client/src/main/resources/client_hello.xml b/opendaylight/netconf/netconf-client/src/main/resources/client_hello.xml new file mode 100644 index 0000000000..1d85b688e5 --- /dev/null +++ b/opendaylight/netconf/netconf-client/src/main/resources/client_hello.xml @@ -0,0 +1,5 @@ + + + urn:ietf:params:netconf:base:1.0 + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-impl/pom.xml b/opendaylight/netconf/netconf-impl/pom.xml new file mode 100644 index 0000000000..b52e85bb21 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/pom.xml @@ -0,0 +1,148 @@ + + + + + netconf-subsystem + org.opendaylight.controller + 0.2.1-SNAPSHOT + + 4.0.0 + netconf-impl + ${project.artifactId} + bundle + + + + + + ${project.groupId} + netconf-api + ${project.version} + + + ${project.groupId} + netconf-util + ${project.version} + + + org.opendaylight.controller + config-util + 0.2.1-SNAPSHOT + + + ${project.groupId} + netconf-mapping-api + ${project.version} + + + + org.opendaylight.bgpcep + util + + + + org.opendaylight.bgpcep + framework + + + + org.osgi + org.osgi.core + + + com.google.guava + guava + + + org.slf4j + slf4j-api + + + + + org.opendaylight.bgpcep + mockito-configuration + test + + + commons-io + commons-io + 2.4 + test + + + + ${project.groupId} + + yang-store-api + test + + + xmlunit + xmlunit + 1.4 + test + + + ${project.groupId} + netconf-util + ${project.version} + test + test-jar + + + ${project.groupId} + netconf-client + ${project.version} + test + + + + + + + org.apache.felix + maven-bundle-plugin + + + org.opendaylight.controller.netconf.impl.osgi.NetconfImplActivator + + com.google.common.base, + com.google.common.collect, + io.netty.channel, + io.netty.channel.socket, + io.netty.util, + io.netty.util.concurrent, + javax.management, + javax.net.ssl, + javax.xml.namespace, + javax.xml.xpath, + org.opendaylight.controller.netconf.api, + org.opendaylight.controller.netconf.api.jmx, + org.opendaylight.controller.netconf.mapping.api, + org.opendaylight.controller.netconf.util, + org.opendaylight.controller.netconf.util.mapping, + org.opendaylight.controller.netconf.util.osgi, + org.opendaylight.controller.netconf.util.xml, + org.opendaylight.protocol.framework, + org.osgi.framework, + org.osgi.util.tracker, + org.slf4j, + org.w3c.dom, + org.xml.sax, + org.opendaylight.controller.netconf.util.messages, + org.opendaylight.controller.config.stat + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + + + diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/CapabilityProviderImpl.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/CapabilityProviderImpl.java new file mode 100644 index 0000000000..1d2e039b29 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/CapabilityProviderImpl.java @@ -0,0 +1,102 @@ +/* + * 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.netconf.impl; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import org.opendaylight.controller.netconf.impl.mapping.CapabilityProvider; +import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationServiceSnapshot; +import org.opendaylight.controller.netconf.mapping.api.Capability; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +public class CapabilityProviderImpl implements CapabilityProvider { + private final NetconfOperationServiceSnapshot netconfOperationServiceSnapshot; + private final Set capabilityURIs; + + public CapabilityProviderImpl(NetconfOperationServiceSnapshot netconfOperationServiceSnapshot) { + this.netconfOperationServiceSnapshot = netconfOperationServiceSnapshot; + Map urisToCapabilitiesInternalMap = getCapabilitiesInternal(netconfOperationServiceSnapshot); + capabilityURIs = Collections.unmodifiableSet(urisToCapabilitiesInternalMap.keySet()); + } + + private static Map getCapabilitiesInternal( + NetconfOperationServiceSnapshot netconfOperationServiceSnapshot) { + Map capabilityMap = Maps.newHashMap(); + + for (NetconfOperationService netconfOperationService : netconfOperationServiceSnapshot.getServices()) { + final Set caps = netconfOperationService.getCapabilities(); + + for (Capability cap : caps) { + // TODO check for duplicates ? + capabilityMap.put(cap.getCapabilityUri(), cap); + } + } + + return capabilityMap; + } + + @Override + public synchronized String getSchemaForCapability(String moduleName, Optional revision) { + + Map> mappedModulesToRevisionToSchema = Maps.newHashMap(); + + for (NetconfOperationService netconfOperationService : netconfOperationServiceSnapshot.getServices()) { + final Set caps = netconfOperationService.getCapabilities(); + + for (Capability cap : caps) { + if (cap.getModuleName().isPresent() == false) + continue; + if (cap.getRevision().isPresent() == false) + continue; + if (cap.getCapabilitySchema().isPresent() == false) + continue; + + final String currentModuleName = cap.getModuleName().get(); + Map revisionMap = mappedModulesToRevisionToSchema.get(currentModuleName); + if (revisionMap == null) { + revisionMap = Maps.newHashMap(); + mappedModulesToRevisionToSchema.put(currentModuleName, revisionMap); + } + + String currentRevision = cap.getRevision().get(); + revisionMap.put(currentRevision, cap.getCapabilitySchema().get()); + } + } + + Map revisionMapRequest = mappedModulesToRevisionToSchema.get(moduleName); + Preconditions.checkState(revisionMapRequest != null, "Capability for module %s not present, " + "" + + "available modules : %s", moduleName, capabilityURIs); + + if (revision.isPresent()) { + String schema = revisionMapRequest.get(revision.get()); + + Preconditions.checkState(schema != null, + "Capability for module %s:%s not present, available revisions for module: %s", moduleName, + revision.get(), revisionMapRequest.keySet()); + + return schema; + } else { + Preconditions.checkState(revisionMapRequest.size() == 1, + "Expected 1 capability for module %s, available revisions : %s", moduleName, + revisionMapRequest.keySet()); + return revisionMapRequest.values().iterator().next(); + } + } + + @Override + public synchronized Set getCapabilities() { + return capabilityURIs; + } + +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/DefaultCommitNotificationProducer.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/DefaultCommitNotificationProducer.java new file mode 100644 index 0000000000..305fb57424 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/DefaultCommitNotificationProducer.java @@ -0,0 +1,57 @@ +/* + * 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.netconf.impl; + +import org.opendaylight.controller.netconf.api.jmx.CommitJMXNotification; +import org.opendaylight.controller.netconf.api.jmx.DefaultCommitOperationMXBean; +import org.opendaylight.controller.netconf.api.jmx.NetconfJMXNotification; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Element; + +import javax.management.*; +import java.util.Set; + +public class DefaultCommitNotificationProducer extends NotificationBroadcasterSupport implements + DefaultCommitOperationMXBean, AutoCloseable { + + private static final Logger logger = LoggerFactory.getLogger(DefaultCommitNotificationProducer.class); + + private final MBeanServer mbeanServer; + + private final ObjectName on = DefaultCommitOperationMXBean.objectName; + + public DefaultCommitNotificationProducer(MBeanServer mBeanServer) { + this.mbeanServer = mBeanServer; + registerMBean(this, mbeanServer, on); + } + + private static void registerMBean(final Object instance, final MBeanServer mbs, final ObjectName on) { + try { + mbs.registerMBean(instance, on); + } catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException e) { + throw new RuntimeException("Unable to register " + instance + " as " + on, e); + } + } + + public void sendCommitNotification(String message, Element cfgSnapshot, Set capabilities) { + CommitJMXNotification notif = NetconfJMXNotification.afterCommit(this, message, cfgSnapshot, capabilities); + logger.debug("Notification about commit {} sent", notif); + sendNotification(notif); + } + + @Override + public void close() { + try { + mbeanServer.unregisterMBean(on); + } catch (InstanceNotFoundException | MBeanRegistrationException e) { + logger.warn("Ignoring exception while unregistering {} as {}", this, on, e); + } + } +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerDispatcher.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerDispatcher.java new file mode 100644 index 0000000000..324da56ca5 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerDispatcher.java @@ -0,0 +1,72 @@ +/* + * 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.netconf.impl; + +import com.google.common.base.Optional; +import io.netty.channel.ChannelFuture; +import io.netty.channel.socket.SocketChannel; +import io.netty.util.concurrent.Promise; +import org.opendaylight.controller.netconf.api.NetconfSession; +import org.opendaylight.controller.netconf.impl.util.DeserializerExceptionHandler; +import org.opendaylight.controller.netconf.util.AbstractChannelInitializer; +import org.opendaylight.protocol.framework.AbstractDispatcher; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import java.net.InetSocketAddress; + +public class NetconfServerDispatcher extends AbstractDispatcher { + + private final ServerChannelInitializer initializer; + + public NetconfServerDispatcher(final Optional maybeContext, + NetconfServerSessionNegotiatorFactory serverNegotiatorFactory, + NetconfServerSessionListenerFactory listenerFactory) { + this.initializer = new ServerChannelInitializer(maybeContext, serverNegotiatorFactory, listenerFactory); + } + + // FIXME change headers for all new source code files + + // TODO test create server with same address twice + public ChannelFuture createServer(InetSocketAddress address) { + + return super.createServer(address, new PipelineInitializer() { + @Override + public void initializeChannel(final SocketChannel ch, final Promise promise) { + initializer.initialize(ch, promise); + } + }); + } + + private static class ServerChannelInitializer extends AbstractChannelInitializer { + + private final NetconfServerSessionNegotiatorFactory negotiatorFactory; + private final NetconfServerSessionListenerFactory listenerFactory; + + private ServerChannelInitializer(Optional maybeContext, + NetconfServerSessionNegotiatorFactory negotiatorFactory, + NetconfServerSessionListenerFactory listenerFactory) { + super(maybeContext); + this.negotiatorFactory = negotiatorFactory; + this.listenerFactory = listenerFactory; + } + + @Override + protected void initializeAfterDecoder(SocketChannel ch, Promise promise) { + ch.pipeline().addLast("deserializerExHandler", new DeserializerExceptionHandler()); + ch.pipeline().addLast("negotiator", negotiatorFactory.getSessionNegotiator(listenerFactory, ch, promise)); + } + + @Override + protected void initSslEngine(SSLEngine sslEngine) { + sslEngine.setUseClientMode(false); + } + } + +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSession.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSession.java new file mode 100644 index 0000000000..1ae3fabbb9 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSession.java @@ -0,0 +1,85 @@ +/* + * 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.netconf.impl; + +import io.netty.channel.Channel; +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.api.NetconfSession; +import org.opendaylight.controller.netconf.api.NetconfTerminationReason; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.opendaylight.protocol.framework.SessionListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +public class NetconfServerSession extends NetconfSession { + + private final SessionListener sessionListener; + private final Channel channel; + + private static final Logger logger = LoggerFactory.getLogger(NetconfServerSession.class); + private final long sessionId; + private boolean up = false; + + public NetconfServerSession(SessionListener sessionListener, Channel channel, long sessionId) { + this.sessionListener = sessionListener; + this.channel = channel; + this.sessionId = sessionId; + logger.debug("Session {} created", toString()); + } + + @Override + public void close() { + channel.close(); + sessionListener.onSessionTerminated(this, new NetconfTerminationReason("Session closed")); + } + + @Override + protected void handleMessage(NetconfMessage netconfMessage) { + logger.debug("Session {} received message {}", toString(), XmlUtil.toString(netconfMessage.getDocument())); + sessionListener.onMessage(this, netconfMessage); + } + + public void sendMessage(NetconfMessage netconfMessage) { + channel.writeAndFlush(netconfMessage); + } + + @Override + protected void endOfInput() { + logger.debug("Session {} end of input detected while session was in state {}", toString(), isUp() ? "up" + : "initialized"); + if (isUp()) { + this.sessionListener.onSessionDown(this, new IOException("End of input detected. Close the session.")); + } + } + + @Override + protected void sessionUp() { + logger.debug("Session {} up", toString()); + sessionListener.onSessionUp(this); + this.up = true; + } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("ServerNetconfSession{"); + sb.append("sessionId=").append(sessionId); + sb.append('}'); + return sb.toString(); + } + + public boolean isUp() { + return up; + } + + public long getSessionId() { + return sessionId; + } +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSessionListener.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSessionListener.java new file mode 100644 index 0000000000..4f71ab9bb5 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSessionListener.java @@ -0,0 +1,127 @@ +/* + * 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.netconf.impl; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +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.impl.osgi.NetconfOperationRouterImpl; +import org.opendaylight.controller.netconf.util.messages.SendErrorExceptionUtil; +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.protocol.framework.SessionListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +import static com.google.common.base.Preconditions.checkState; + +public class NetconfServerSessionListener implements + SessionListener { + + static final Logger logger = LoggerFactory.getLogger(NetconfServerSessionListener.class); + public static final String MESSAGE_ID = "message-id"; + + private NetconfOperationRouterImpl operationRouter; + + public NetconfServerSessionListener(NetconfOperationRouterImpl operationRouter) { + this.operationRouter = operationRouter; + } + + @Override + public void onSessionUp(NetconfServerSession netconfNetconfServerSession) { + + } + + @Override + public void onSessionDown(NetconfServerSession netconfNetconfServerSession, Exception e) { + logger.debug("Session {} down, reason: {}", netconfNetconfServerSession, e.getMessage()); + + operationRouter.close(); + } + + @Override + public void onSessionTerminated(NetconfServerSession netconfNetconfServerSession, + NetconfTerminationReason netconfTerminationReason) { + logger.debug("Session {} terminated, reason: {}", netconfNetconfServerSession, + netconfTerminationReason.getErrorMessage()); + + operationRouter.close(); + } + + @Override + public void onMessage(NetconfServerSession session, NetconfMessage netconfMessage) { + try { + + Preconditions.checkState(operationRouter != null, "Cannot handle message, session up was not yet received"); + // FIXME: there is no validation since the document may contain yang + // schemas + final NetconfMessage message = processDocument(netconfMessage); + logger.debug("Respondign with message {}", XmlUtil.toString(message.getDocument())); + session.sendMessage(message); + + if (isCloseSession(netconfMessage)) { + closeNetconfSession(session); + } + + } catch (final RuntimeException e) { + logger.error("Unexpected exception", e); + // TODO: should send generic error or close session? + throw new RuntimeException("Unable to process incoming message " + netconfMessage, e); + } catch (NetconfDocumentedException e) { + SendErrorExceptionUtil.sendErrorMessage(session, e, netconfMessage); + } + } + + private void closeNetconfSession(NetconfServerSession session) { + // destroy NetconfOperationService + session.close(); + logger.info("Session {} closed successfully", session.getSessionId()); + } + + private NetconfMessage processDocument(final NetconfMessage netconfMessage) throws NetconfDocumentedException { + + final Document incommingDocument = netconfMessage.getDocument(); + final Node rootNode = incommingDocument.getDocumentElement(); + + if (rootNode.getNodeName().equals(XmlNetconfConstants.RPC_KEY)) { + final String messageId = rootNode.getAttributes().getNamedItem(MESSAGE_ID).getTextContent(); + checkState(messageId != null); + final Document responseDocument = XmlUtil.newDocument(); + Document rpcReply = operationRouter.onNetconfMessage(incommingDocument); + responseDocument.appendChild(responseDocument.importNode(rpcReply.getDocumentElement(), true)); + return new NetconfMessage(responseDocument); + } else { + // unknown command, send RFC 4741 p.70 unknown-element + /* + * Tag: unknown-element Error-type: rpc, protocol, application + * Severity: error Error-info: : name of the + * unexpected element Description: An unexpected element is present. + */ + // TODO add message to error info + throw new NetconfDocumentedException("Unknown tag " + rootNode.getNodeName(), + NetconfDocumentedException.ErrorType.protocol, NetconfDocumentedException.ErrorTag.unknown_element, + NetconfDocumentedException.ErrorSeverity.error, ImmutableMap.of("bad-element", + rootNode.getNodeName())); + } + } + + private static boolean isCloseSession(final NetconfMessage incommingDocument) { + final Document document = incommingDocument.getDocument(); + XmlElement rpcElement = XmlElement.fromDomDocument(document); + if (rpcElement.getOnlyChildElementOptionally("close-session").isPresent()) + return true; + + return false; + } +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSessionListenerFactory.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSessionListenerFactory.java new file mode 100644 index 0000000000..eea6dc19da --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSessionListenerFactory.java @@ -0,0 +1,44 @@ +/* + * 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.netconf.impl; + +import org.opendaylight.controller.netconf.impl.mapping.CapabilityProvider; +import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationRouterImpl; +import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationServiceFactoryListener; +import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationServiceSnapshot; +import org.opendaylight.protocol.framework.SessionListenerFactory; + +public class NetconfServerSessionListenerFactory implements SessionListenerFactory { + + private final NetconfOperationServiceFactoryListener factoriesListener; + + private final DefaultCommitNotificationProducer commitNotifier; + + private final SessionIdProvider idProvider; + + public NetconfServerSessionListenerFactory(NetconfOperationServiceFactoryListener factoriesListener, + DefaultCommitNotificationProducer commitNotifier, SessionIdProvider idProvider) { + this.factoriesListener = factoriesListener; + this.commitNotifier = commitNotifier; + this.idProvider = idProvider; + } + + @Override + public NetconfServerSessionListener getSessionListener() { + NetconfOperationServiceSnapshot netconfOperationServiceSnapshot = factoriesListener.getSnapshot(idProvider + .getCurrentSessionId()); + + CapabilityProvider capabilityProvider = new CapabilityProviderImpl(netconfOperationServiceSnapshot); + + NetconfOperationRouterImpl operationRouter = new NetconfOperationRouterImpl(netconfOperationServiceSnapshot, + capabilityProvider, commitNotifier); + + return new NetconfServerSessionListener(operationRouter); + } +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSessionNegotiator.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSessionNegotiator.java new file mode 100644 index 0000000000..e14ae3e4dc --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSessionNegotiator.java @@ -0,0 +1,33 @@ +/* + * 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.netconf.impl; + +import org.opendaylight.controller.netconf.api.NetconfServerSessionPreferences; +import org.opendaylight.controller.netconf.util.AbstractNetconfSessionNegotiator; +import org.opendaylight.protocol.framework.SessionListener; +import org.w3c.dom.Document; + +import io.netty.channel.Channel; +import io.netty.util.Timer; +import io.netty.util.concurrent.Promise; + +public class NetconfServerSessionNegotiator extends + AbstractNetconfSessionNegotiator { + + protected NetconfServerSessionNegotiator(NetconfServerSessionPreferences sessionPreferences, + Promise promise, Channel channel, Timer timer, SessionListener sessionListener) { + super(sessionPreferences, promise, channel, timer, sessionListener); + } + + @Override + protected NetconfServerSession getSession(SessionListener sessionListener, Channel channel, Document doc) { + return new NetconfServerSession(sessionListener, channel, sessionPreferences.getSessionId()); + } + +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSessionNegotiatorFactory.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSessionNegotiatorFactory.java new file mode 100644 index 0000000000..e74723032d --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSessionNegotiatorFactory.java @@ -0,0 +1,93 @@ +/* + * 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.netconf.impl; + +import io.netty.channel.Channel; +import io.netty.util.Timer; +import io.netty.util.concurrent.Promise; +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.api.NetconfServerSessionPreferences; +import org.opendaylight.controller.netconf.impl.mapping.CapabilityProvider; +import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationServiceFactoryListener; +import org.opendaylight.controller.netconf.impl.util.NetconfUtil; +import org.opendaylight.controller.netconf.util.xml.XMLNetconfUtil; +import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.opendaylight.protocol.framework.SessionListenerFactory; +import org.opendaylight.protocol.framework.SessionNegotiator; +import org.opendaylight.protocol.framework.SessionNegotiatorFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; + +public class NetconfServerSessionNegotiatorFactory implements SessionNegotiatorFactory { + + private final Timer timer; + + private static final Document helloMessageTemplate = loadHelloMessageTemplate(); + private final SessionIdProvider idProvider; + private final NetconfOperationServiceFactoryListener factoriesListener; + + public NetconfServerSessionNegotiatorFactory(Timer timer, NetconfOperationServiceFactoryListener factoriesListener, + SessionIdProvider idProvider) { + this.timer = timer; + this.factoriesListener = factoriesListener; + this.idProvider = idProvider; + } + + private static Document loadHelloMessageTemplate() { + return NetconfUtil.createMessage( + NetconfServerSessionNegotiatorFactory.class.getResourceAsStream("/server_hello.xml")).getDocument(); + } + + @Override + public SessionNegotiator getSessionNegotiator(SessionListenerFactory sessionListenerFactory, Channel channel, + Promise promise) { + long sessionId = idProvider.getNextSessionId(); + + NetconfServerSessionPreferences proposal = new NetconfServerSessionPreferences(createHelloMessage(sessionId), + sessionId); + return new NetconfServerSessionNegotiator(proposal, promise, channel, timer, + sessionListenerFactory.getSessionListener()); + } + + private static final XPathExpression sessionIdXPath = XMLNetconfUtil + .compileXPath("/netconf:hello/netconf:session-id"); + private static final XPathExpression capabilitiesXPath = XMLNetconfUtil + .compileXPath("/netconf:hello/netconf:capabilities"); + + private NetconfMessage createHelloMessage(long sessionId) { + Document helloMessageTemplate = getHelloTemplateClone(); + + // change session ID + final Node sessionIdNode = (Node) XmlUtil.evaluateXPath(sessionIdXPath, helloMessageTemplate, + XPathConstants.NODE); + sessionIdNode.setTextContent(String.valueOf(sessionId)); + + // add capabilities from yang store + final Element capabilitiesElement = (Element) XmlUtil.evaluateXPath(capabilitiesXPath, helloMessageTemplate, + XPathConstants.NODE); + + CapabilityProvider capabilityProvider = new CapabilityProviderImpl(factoriesListener.getSnapshot(sessionId)); + + for (String capability : capabilityProvider.getCapabilities()) { + final Element capabilityElement = helloMessageTemplate.createElement(XmlNetconfConstants.CAPABILITY); + capabilityElement.setTextContent(capability); + capabilitiesElement.appendChild(capabilityElement); + } + return new NetconfMessage(helloMessageTemplate); + } + + private synchronized Document getHelloTemplateClone() { + return (Document) this.helloMessageTemplate.cloneNode(true); + } +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/SessionIdProvider.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/SessionIdProvider.java new file mode 100644 index 0000000000..60fbfd8196 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/SessionIdProvider.java @@ -0,0 +1,24 @@ +/* + * 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.netconf.impl; + +import java.util.concurrent.atomic.AtomicLong; + +public final class SessionIdProvider { + + private final AtomicLong sessionCounter = new AtomicLong(0); + + public long getNextSessionId() { + return sessionCounter.incrementAndGet(); + } + + public long getCurrentSessionId() { + return sessionCounter.get(); + } +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/CapabilityProvider.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/CapabilityProvider.java new file mode 100644 index 0000000000..b785b2d2ab --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/CapabilityProvider.java @@ -0,0 +1,21 @@ +/* + * 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.netconf.impl.mapping; + +import com.google.common.base.Optional; + +import java.util.Set; + +public interface CapabilityProvider { + + String getSchemaForCapability(String moduleName, Optional revision); + + Set getCapabilities(); + +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultCloseSession.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultCloseSession.java new file mode 100644 index 0000000000..1438515f04 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultCloseSession.java @@ -0,0 +1,48 @@ +/* + * 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.netconf.impl.mapping.operations; + +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfOperationRouter; +import org.opendaylight.controller.netconf.mapping.api.HandlingPriority; +import org.opendaylight.controller.netconf.util.mapping.AbstractNetconfOperation; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class DefaultCloseSession extends AbstractNetconfOperation { + public static final String CLOSE_SESSION = "close-session"; + + public DefaultCloseSession(String netconfSessionIdForReporting) { + super(netconfSessionIdForReporting); + } + + @Override + protected HandlingPriority canHandle(String operationName, String netconfOperationNamespace) { + if (operationName.equals(CLOSE_SESSION) == false) + return HandlingPriority.CANNOT_HANDLE; + if (netconfOperationNamespace.equals(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0) == false) + return HandlingPriority.CANNOT_HANDLE; + + return HandlingPriority.HANDLE_WITH_MAX_PRIORITY; + } + + /** + * Close netconf operation router associated to this session, which in turn + * closes NetconfOperationServiceSnapshot with all NetconfOperationService + * instances + */ + @Override + protected Element handle(Document document, XmlElement operationElement, NetconfOperationRouter opRouter) + throws NetconfDocumentedException { + opRouter.close(); + return document.createElement(XmlNetconfConstants.OK); + } +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultCommit.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultCommit.java new file mode 100644 index 0000000000..8637c0dd74 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultCommit.java @@ -0,0 +1,144 @@ +/* + * 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.netconf.impl.mapping.operations; + +import com.google.common.collect.Maps; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfOperationRouter; +import org.opendaylight.controller.netconf.impl.DefaultCommitNotificationProducer; +import org.opendaylight.controller.netconf.impl.mapping.CapabilityProvider; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationFilter; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationFilterChain; +import org.opendaylight.controller.netconf.util.mapping.AbstractNetconfOperation.OperationNameAndNamespace; +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.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import java.io.InputStream; +import java.util.Map; + +public class DefaultCommit implements NetconfOperationFilter { + + private static final Logger logger = LoggerFactory.getLogger(DefaultCommit.class); + + private static final String NOTIFY_ATTR = "notify"; + + private final DefaultCommitNotificationProducer notificationProducer; + private final CapabilityProvider cap; + private final String netconfSessionIdForReporting; + + public DefaultCommit(DefaultCommitNotificationProducer notifier, CapabilityProvider cap, + String netconfSessionIdForReporting) { + this.notificationProducer = notifier; + this.cap = cap; + this.netconfSessionIdForReporting = netconfSessionIdForReporting; + this.getConfigMessage = loadGetConfigMessage(); + } + + private final Document getConfigMessage; + public static final String GET_CONFIG_CANDIDATE_XML_LOCATION = "/getConfig_candidate.xml"; + + private static Document loadGetConfigMessage() { + try (InputStream asStream = DefaultCommit.class.getResourceAsStream(GET_CONFIG_CANDIDATE_XML_LOCATION)) { + return XmlUtil.readXmlToDocument(asStream); + } catch (Exception e) { + throw new IllegalStateException("Unable to load getConfig message for notifications from " + + GET_CONFIG_CANDIDATE_XML_LOCATION); + } + } + + @Override + public Document doFilter(Document message, NetconfOperationRouter operationRouter, + NetconfOperationFilterChain filterChain) throws NetconfDocumentedException { + OperationNameAndNamespace operationNameAndNamespace = new OperationNameAndNamespace(message); + if (canHandle(operationNameAndNamespace)) { + if (isCommitWithoutNotification(message)) { + message = removePersisterAttributes(message); + logger.debug("Skipping commit notification"); + // fall back to filter chain + } else { + Document innerResult = filterChain.execute(message, operationRouter); + Element cfgSnapshot = getConfigSnapshot(operationRouter); + logger.debug("Config snapshot retrieved successfully {}", cfgSnapshot); + notificationProducer.sendCommitNotification("ok", cfgSnapshot, cap.getCapabilities()); + return innerResult; + } + } + return filterChain.execute(message, operationRouter); + } + + @Override + public int getSoringOrder() { + return 0; + } + + @Override + public int compareTo(NetconfOperationFilter o) { + return Integer.compare(getSoringOrder(), o.getSoringOrder()); + } + + private boolean canHandle(OperationNameAndNamespace operationNameAndNamespace) { + if (operationNameAndNamespace.getOperationName().equals("commit") == false) + return false; + return operationNameAndNamespace.getNamespace().equals( + XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0); + } + + private Document removePersisterAttributes(Document message) { + final Element documentElement = message.getDocumentElement(); + documentElement.removeAttribute(NOTIFY_ATTR); + return message; + } + + private boolean isCommitWithoutNotification(Document message) { + XmlElement xmlElement = XmlElement.fromDomElementWithExpected(message.getDocumentElement(), + XmlNetconfConstants.RPC_KEY, XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0); + + String attr = xmlElement.getAttribute(NOTIFY_ATTR); + + if (attr == null || attr.equals("")) + return false; + else if (attr.equals(Boolean.toString(false))) { + logger.debug("Commit operation received with notify=false attribute {}", message); + return true; + } else { + return false; + } + } + + private Element getConfigSnapshot(NetconfOperationRouter opRouter) throws NetconfDocumentedException { + final Document responseDocument = opRouter.onNetconfMessage(getConfigMessage); + + XmlElement dataElement; + try { + XmlElement xmlElement = XmlElement.fromDomElementWithExpected(responseDocument.getDocumentElement(), + XmlNetconfConstants.RPC_REPLY_KEY, XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0); + dataElement = xmlElement.getOnlyChildElement(XmlNetconfConstants.DATA_KEY); + } catch (IllegalArgumentException e) { + final String msg = "Unexpected response from get-config operation"; + logger.warn(msg, e); + Map info = Maps.newHashMap(); + info.put(NetconfDocumentedException.ErrorTag.operation_failed.toString(), e.getMessage()); + throw new NetconfDocumentedException(msg, e, NetconfDocumentedException.ErrorType.application, + NetconfDocumentedException.ErrorTag.operation_failed, + NetconfDocumentedException.ErrorSeverity.error, info); + } + + return dataElement.getDomElement(); + } + + @Override + public String toString() { + return "DefaultCommit{" + netconfSessionIdForReporting + '}'; + } +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultGetSchema.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultGetSchema.java new file mode 100644 index 0000000000..3e65ed8842 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultGetSchema.java @@ -0,0 +1,119 @@ +/* + * 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.netconf.impl.mapping.operations; + +import com.google.common.base.Optional; +import com.google.common.collect.Maps; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfOperationRouter; +import org.opendaylight.controller.netconf.impl.mapping.CapabilityProvider; +import org.opendaylight.controller.netconf.mapping.api.HandlingPriority; +import org.opendaylight.controller.netconf.util.mapping.AbstractNetconfOperation; +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.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import java.util.HashMap; +import java.util.Map; + +public final class DefaultGetSchema extends AbstractNetconfOperation { + + private final CapabilityProvider cap; + + private static final Logger logger = LoggerFactory.getLogger(DefaultGetSchema.class); + + public DefaultGetSchema(CapabilityProvider cap, String netconfSessionIdForReporting) { + super(netconfSessionIdForReporting); + this.cap = cap; + } + + public static final String GET_SCHEMA = "get-schema"; + public static final String IDENTIFIER = "identifier"; + public static final String VERSION = "version"; + + @Override + protected HandlingPriority canHandle(String netconfOperationName, String namespace) { + if (netconfOperationName.equals("get-schema") == false) + return HandlingPriority.CANNOT_HANDLE; + if (namespace.equals(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_YANG_IETF_NETCONF_MONITORING) == false) + return HandlingPriority.CANNOT_HANDLE; + + return HandlingPriority.HANDLE_WITH_DEFAULT_PRIORITY; + } + + @Override + protected Element handle(Document document, XmlElement xml, NetconfOperationRouter router) + throws NetconfDocumentedException { + GetSchemaEntry entry; + + try { + entry = new GetSchemaEntry(xml); + } catch (final IllegalArgumentException e) { + logger.warn("Error parsing xml", e); + final Map errorInfo = new HashMap<>(); + errorInfo.put(NetconfDocumentedException.ErrorTag.bad_attribute.name(), e.getMessage()); + throw new NetconfDocumentedException(e.getMessage(), e, NetconfDocumentedException.ErrorType.rpc, + NetconfDocumentedException.ErrorTag.bad_attribute, NetconfDocumentedException.ErrorSeverity.error, + errorInfo); + } catch (final IllegalStateException e) { + logger.warn("Error parsing xml", e); + final Map errorInfo = new HashMap<>(); + errorInfo.put(NetconfDocumentedException.ErrorTag.bad_attribute.name(), e.getMessage()); + throw new NetconfDocumentedException(e.getMessage(), e, NetconfDocumentedException.ErrorType.rpc, + NetconfDocumentedException.ErrorTag.bad_attribute, NetconfDocumentedException.ErrorSeverity.error, + errorInfo); + } + + String schema; + try { + schema = cap.getSchemaForCapability(entry.identifier, entry.version); + } catch (IllegalStateException e) { + Map errorInfo = Maps.newHashMap(); + errorInfo.put(entry.identifier, e.getMessage()); + logger.warn("Rpc error: {}", NetconfDocumentedException.ErrorTag.operation_failed, e); + throw new NetconfDocumentedException(e.getMessage(), NetconfDocumentedException.ErrorType.application, + NetconfDocumentedException.ErrorTag.operation_failed, + NetconfDocumentedException.ErrorSeverity.error, errorInfo); + } + + Element getSchemaResult; + getSchemaResult = XmlUtil.createTextElement(document, XmlNetconfConstants.DATA_KEY, schema); + XmlUtil.addNamespaceAttr(getSchemaResult, + XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_YANG_IETF_NETCONF_MONITORING); + + logger.info("{} operation successful", GET_SCHEMA); + + return getSchemaResult; + } + + private static final class GetSchemaEntry { + private final String identifier; + private final Optional version; + + GetSchemaEntry(XmlElement getSchemaElement) { + getSchemaElement.checkName(GET_SCHEMA); + getSchemaElement.checkNamespace(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_YANG_IETF_NETCONF_MONITORING); + + XmlElement identifierElement = getSchemaElement.getOnlyChildElementWithSameNamespace(IDENTIFIER); + identifier = identifierElement.getTextContent(); + Optional versionElement = getSchemaElement + .getOnlyChildElementWithSameNamespaceOptionally(VERSION); + if (versionElement.isPresent()) { + version = Optional.of(versionElement.get().getTextContent()); + } else { + version = Optional.absent(); + } + + } + } +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfImplActivator.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfImplActivator.java new file mode 100644 index 0000000000..96da1a6018 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfImplActivator.java @@ -0,0 +1,82 @@ +/* + * 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.netconf.impl.osgi; + +import com.google.common.base.Optional; +import io.netty.util.HashedWheelTimer; +import org.opendaylight.controller.config.stat.ConfigProvider; +import org.opendaylight.controller.netconf.impl.*; +import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil; +import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil.TLSConfiguration; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.SSLContext; +import java.lang.management.ManagementFactory; +import java.net.InetSocketAddress; + +public class NetconfImplActivator implements BundleActivator { + + private static final Logger logger = LoggerFactory.getLogger(NetconfImplActivator.class); + + private Optional maybeTCPAddress; + private Optional maybeTLSConfiguration; + + private NetconfOperationServiceFactoryTracker factoriesTracker; + private DefaultCommitNotificationProducer commitNot; + private NetconfServerDispatcher dispatch; + + @Override + public void start(final BundleContext context) throws Exception { + final ConfigProvider configProvider = new ConfigProvider.ConfigProviderImpl(context); + maybeTCPAddress = NetconfConfigUtil.extractTCPNetconfAddress(configProvider); + maybeTLSConfiguration = NetconfConfigUtil.extractTLSConfiguration(configProvider); + if (maybeTCPAddress.isPresent() == false && maybeTLSConfiguration.isPresent() == false) { + throw new IllegalStateException("TCP nor TLS is configured, netconf not available."); + } + NetconfOperationServiceFactoryListenerImpl factoriesListener = new NetconfOperationServiceFactoryListenerImpl(); + factoriesTracker = new NetconfOperationServiceFactoryTracker(context, factoriesListener); + factoriesTracker.open(); + + SessionIdProvider idProvider = new SessionIdProvider(); + NetconfServerSessionNegotiatorFactory serverNegotiatorFactory = new NetconfServerSessionNegotiatorFactory( + new HashedWheelTimer(), factoriesListener, idProvider); + + commitNot = new DefaultCommitNotificationProducer(ManagementFactory.getPlatformMBeanServer()); + + NetconfServerSessionListenerFactory listenerFactory = new NetconfServerSessionListenerFactory( + factoriesListener, commitNot, idProvider); + + if (maybeTCPAddress.isPresent()) { + Optional maybeSSLContext = Optional.absent(); + InetSocketAddress address = maybeTCPAddress.get(); + dispatch = new NetconfServerDispatcher(maybeSSLContext, serverNegotiatorFactory, listenerFactory); + + logger.info("Starting TCP netconf server at {}", address); + dispatch.createServer(address); + } + if (maybeTLSConfiguration.isPresent()) { + Optional maybeSSLContext = Optional.of(maybeTLSConfiguration.get().getSslContext()); + InetSocketAddress address = maybeTLSConfiguration.get().getAddress(); + dispatch = new NetconfServerDispatcher(maybeSSLContext, serverNegotiatorFactory, listenerFactory); + + logger.info("Starting TLS netconf server at {}", address); + dispatch.createServer(address); + } + } + + @Override + public void stop(final BundleContext context) throws Exception { + logger.info("Shutting down netconf because YangStoreService service was removed"); + + commitNot.close(); + dispatch.close(); + } +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationRouterImpl.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationRouterImpl.java new file mode 100644 index 0000000000..c7e1740ebc --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationRouterImpl.java @@ -0,0 +1,190 @@ +/* + * 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.netconf.impl.osgi; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfOperationRouter; +import org.opendaylight.controller.netconf.impl.DefaultCommitNotificationProducer; +import org.opendaylight.controller.netconf.impl.mapping.CapabilityProvider; +import org.opendaylight.controller.netconf.impl.mapping.operations.DefaultCloseSession; +import org.opendaylight.controller.netconf.impl.mapping.operations.DefaultCommit; +import org.opendaylight.controller.netconf.impl.mapping.operations.DefaultGetSchema; +import org.opendaylight.controller.netconf.mapping.api.*; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; + +import java.util.*; + +public class NetconfOperationRouterImpl implements NetconfOperationRouter { + + private static final Logger logger = LoggerFactory.getLogger(NetconfOperationRouterImpl.class); + + private final NetconfOperationServiceSnapshot netconfOperationServiceSnapshot; + + private final Set allNetconfOperations; + private final TreeSet allSortedFilters; + + private final CapabilityProvider capabilityProvider; + + public NetconfOperationRouterImpl(NetconfOperationServiceSnapshot netconfOperationServiceSnapshot, + CapabilityProvider capabilityProvider, DefaultCommitNotificationProducer commitNotifier) { + + this.netconfOperationServiceSnapshot = netconfOperationServiceSnapshot; + + this.capabilityProvider = capabilityProvider; + + Set defaultNetconfOperations = Sets.newHashSet(); + defaultNetconfOperations.add(new DefaultGetSchema(capabilityProvider, netconfOperationServiceSnapshot + .getNetconfSessionIdForReporting())); + defaultNetconfOperations.add(new DefaultCloseSession(netconfOperationServiceSnapshot + .getNetconfSessionIdForReporting())); + + allNetconfOperations = getAllNetconfOperations(defaultNetconfOperations, netconfOperationServiceSnapshot); + + DefaultCommit defaultCommit = new DefaultCommit(commitNotifier, capabilityProvider, + netconfOperationServiceSnapshot.getNetconfSessionIdForReporting()); + Set defaultFilters = Sets. newHashSet(defaultCommit); + allSortedFilters = getAllNetconfFilters(defaultFilters, netconfOperationServiceSnapshot); + } + + private static Set getAllNetconfOperations(Set defaultNetconfOperations, + NetconfOperationServiceSnapshot netconfOperationServiceSnapshot) { + Set result = new HashSet<>(); + result.addAll(defaultNetconfOperations); + + for (NetconfOperationService netconfOperationService : netconfOperationServiceSnapshot.getServices()) { + final Set netOpsFromService = netconfOperationService.getNetconfOperations(); + for (NetconfOperation netconfOperation : netOpsFromService) { + Preconditions.checkState(result.contains(netconfOperation) == false, + "Netconf operation %s already present", netconfOperation); + result.add(netconfOperation); + } + } + return Collections.unmodifiableSet(result); + } + + private static TreeSet getAllNetconfFilters(Set defaultFilters, + NetconfOperationServiceSnapshot netconfOperationServiceSnapshot) { + TreeSet result = new TreeSet<>(defaultFilters); + for (NetconfOperationService netconfOperationService : netconfOperationServiceSnapshot.getServices()) { + final Set filtersFromService = netconfOperationService.getFilters(); + for (NetconfOperationFilter filter : filtersFromService) { + Preconditions.checkState(result.contains(filter) == false, "Filter %s already present", filter); + result.add(filter); + } + } + return result; + } + + public CapabilityProvider getCapabilityProvider() { + return capabilityProvider; + } + + @Override + public synchronized Document onNetconfMessage(Document message) throws NetconfDocumentedException { + NetconfOperationExecution netconfOperationExecution = getNetconfOperationWithHighestPriority(message); + logger.debug("Forwarding netconf message {} to {}", XmlUtil.toString(message), + netconfOperationExecution.operationWithHighestPriority); + + final LinkedList chain = new LinkedList<>(); + chain.push(netconfOperationExecution); + + for (Iterator it = allSortedFilters.descendingIterator(); it.hasNext();) { + final NetconfOperationFilter filter = it.next(); + final NetconfOperationFilterChain prevItem = chain.getFirst(); + NetconfOperationFilterChain currentItem = new NetconfOperationFilterChain() { + @Override + public Document execute(Document message, NetconfOperationRouter operationRouter) + throws NetconfDocumentedException { + logger.trace("Entering {}", filter); + return filter.doFilter(message, operationRouter, prevItem); + } + }; + chain.push(currentItem); + } + return chain.getFirst().execute(message, this); + } + + private NetconfOperationExecution getNetconfOperationWithHighestPriority(Document message) { + + // TODO test + TreeMap> sortedPriority = getSortedNetconfOperationsWithCanHandle(message); + + Preconditions.checkState(sortedPriority.isEmpty() == false, "No %s available to handle message %s", + NetconfOperation.class.getName(), XmlUtil.toString(message)); + + HandlingPriority highestFoundPriority = sortedPriority.lastKey(); + + int netconfOperationsWithHighestPriority = sortedPriority.get(highestFoundPriority).size(); + + Preconditions.checkState(netconfOperationsWithHighestPriority == 1, + "Multiple %s available to handle message %s", NetconfOperation.class.getName(), message); + + return new NetconfOperationExecution(sortedPriority, highestFoundPriority); + } + + private TreeMap> getSortedNetconfOperationsWithCanHandle(Document message) { + TreeMap> sortedPriority = Maps.newTreeMap(); + + for (NetconfOperation netconfOperation : allNetconfOperations) { + final HandlingPriority handlingPriority = netconfOperation.canHandle(message); + + if (handlingPriority.equals(HandlingPriority.CANNOT_HANDLE) == false) { + Set netconfOperations = sortedPriority.get(handlingPriority); + netconfOperations = checkIfNoOperationsOnPriority(sortedPriority, handlingPriority, netconfOperations); + netconfOperations.add(netconfOperation); + } + } + return sortedPriority; + } + + private Set checkIfNoOperationsOnPriority( + TreeMap> sortedPriority, HandlingPriority handlingPriority, + Set netconfOperations) { + if (netconfOperations == null) { + netconfOperations = Sets.newHashSet(); + sortedPriority.put(handlingPriority, netconfOperations); + } + return netconfOperations; + } + + @Override + public void close() { + netconfOperationServiceSnapshot.close(); + } + + private class NetconfOperationExecution implements NetconfOperationFilterChain { + private final NetconfOperation operationWithHighestPriority; + + private NetconfOperationExecution(NetconfOperation operationWithHighestPriority) { + this.operationWithHighestPriority = operationWithHighestPriority; + } + + public NetconfOperationExecution(TreeMap> sortedPriority, + HandlingPriority highestFoundPriority) { + operationWithHighestPriority = sortedPriority.get(highestFoundPriority).iterator().next(); + sortedPriority.remove(highestFoundPriority); + } + + @Override + public Document execute(Document message, NetconfOperationRouter router) throws NetconfDocumentedException { + return operationWithHighestPriority.handle(message, router); + } + } + + @Override + public String toString() { + return "NetconfOperationRouterImpl{" + "netconfOperationServiceSnapshot=" + netconfOperationServiceSnapshot + + '}'; + } +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceFactoryListener.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceFactoryListener.java new file mode 100644 index 0000000000..385ad09754 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceFactoryListener.java @@ -0,0 +1,20 @@ +/* + * 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.netconf.impl.osgi; + +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; + +public interface NetconfOperationServiceFactoryListener { + + void onAddNetconfOperationServiceFactory(NetconfOperationServiceFactory service); + + void onRemoveNetconfOperationServiceFactory(NetconfOperationServiceFactory service); + + NetconfOperationServiceSnapshot getSnapshot(long sessionId); +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceFactoryListenerImpl.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceFactoryListenerImpl.java new file mode 100644 index 0000000000..134c38ba97 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceFactoryListenerImpl.java @@ -0,0 +1,33 @@ +/* + * 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.netconf.impl.osgi; + +import java.util.HashSet; +import java.util.Set; + +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; + +public class NetconfOperationServiceFactoryListenerImpl implements NetconfOperationServiceFactoryListener { + private final Set allFactories = new HashSet<>(); + + @Override + public synchronized void onAddNetconfOperationServiceFactory(NetconfOperationServiceFactory service) { + allFactories.add(service); + } + + @Override + public synchronized void onRemoveNetconfOperationServiceFactory(NetconfOperationServiceFactory service) { + allFactories.remove(service); + } + + @Override + public synchronized NetconfOperationServiceSnapshot getSnapshot(long sessionId) { + return new NetconfOperationServiceSnapshot(allFactories, sessionId); + } + +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceFactoryTracker.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceFactoryTracker.java new file mode 100644 index 0000000000..4d2bcb34fa --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceFactoryTracker.java @@ -0,0 +1,38 @@ +/* + * 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.netconf.impl.osgi; + +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.util.tracker.ServiceTracker; + +class NetconfOperationServiceFactoryTracker extends + ServiceTracker { + private final NetconfOperationServiceFactoryListener operationRouter; + + NetconfOperationServiceFactoryTracker(BundleContext context, + final NetconfOperationServiceFactoryListener operationRouter) { + super(context, NetconfOperationServiceFactory.class, null); + this.operationRouter = operationRouter; + } + + @Override + public NetconfOperationServiceFactory addingService(ServiceReference reference) { + NetconfOperationServiceFactory netconfOperationServiceFactory = super.addingService(reference); + operationRouter.onAddNetconfOperationServiceFactory(netconfOperationServiceFactory); + return netconfOperationServiceFactory; + } + + @Override + public void removedService(ServiceReference reference, + NetconfOperationServiceFactory netconfOperationServiceFactory) { + operationRouter.onRemoveNetconfOperationServiceFactory(netconfOperationServiceFactory); + } + +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceSnapshot.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceSnapshot.java new file mode 100644 index 0000000000..cb4f53257e --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceSnapshot.java @@ -0,0 +1,71 @@ +/* + * 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.netconf.impl.osgi; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class NetconfOperationServiceSnapshot implements AutoCloseable { + private static final Logger logger = LoggerFactory.getLogger(NetconfOperationServiceSnapshot.class); + + private final Set services; + private final String netconfSessionIdForReporting; + + public NetconfOperationServiceSnapshot(Set factories, long sessionId) { + Set services = new HashSet<>(); + netconfSessionIdForReporting = getNetconfSessionIdForReporting(sessionId); + for (NetconfOperationServiceFactory factory : factories) { + services.add(factory.createService(sessionId, netconfSessionIdForReporting)); + } + this.services = Collections.unmodifiableSet(services); + } + + private static String getNetconfSessionIdForReporting(long sessionId) { + return "netconf session id " + sessionId; + } + + public String getNetconfSessionIdForReporting() { + return netconfSessionIdForReporting; + } + + public Set getServices() { + return services; + } + + @Override + public void close() { + RuntimeException firstException = null; + for (NetconfOperationService service : services) { + try { + service.close(); + } catch (RuntimeException e) { + logger.warn("Got exception while closing {}", service, e); + if (firstException == null) { + firstException = e; + } else { + firstException.addSuppressed(e); + } + } + } + if (firstException != null) { + throw firstException; + } + } + + @Override + public String toString() { + return "NetconfOperationServiceSnapshot{" + netconfSessionIdForReporting + '}'; + } +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/util/DeserializerExceptionHandler.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/util/DeserializerExceptionHandler.java new file mode 100644 index 0000000000..b27cd20172 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/util/DeserializerExceptionHandler.java @@ -0,0 +1,51 @@ +/* + * 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.netconf.impl.util; + +import java.util.Map; + +import org.opendaylight.controller.netconf.api.NetconfDeserializerException; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.util.messages.SendErrorExceptionUtil; + +import com.google.common.collect.Maps; + +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; + +public final class DeserializerExceptionHandler implements ChannelHandler { + + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + // NOOP + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + // NOOP + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + if (cause instanceof NetconfDeserializerException) { + handleDeserializerException(ctx, cause); + } + } + + private void handleDeserializerException(ChannelHandlerContext ctx, Throwable cause) { + + Map info = Maps.newHashMap(); + info.put("cause", cause.getMessage()); + NetconfDocumentedException ex = new NetconfDocumentedException(cause.getMessage(), + NetconfDocumentedException.ErrorType.rpc, NetconfDocumentedException.ErrorTag.malformed_message, + NetconfDocumentedException.ErrorSeverity.error, info); + + SendErrorExceptionUtil.sendErrorMessage(ctx.channel(), ex); + } +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/util/NetconfUtil.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/util/NetconfUtil.java new file mode 100644 index 0000000000..0aa6fb31b7 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/util/NetconfUtil.java @@ -0,0 +1,37 @@ +package org.opendaylight.controller.netconf.impl.util; + +import java.io.*; + +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +// TODO purge nulls +public class NetconfUtil { + + private static final Logger logger = LoggerFactory.getLogger(NetconfUtil.class); + + public static NetconfMessage createMessage(final File f) { + try { + return createMessage(new FileInputStream(f)); + } catch (final FileNotFoundException e) { + logger.warn("File {} not found.", f, e); + } + return null; + } + + public static NetconfMessage createMessage(final InputStream is) { + Document doc = null; + try { + doc = XmlUtil.readXmlToDocument(is); + } catch (final IOException e) { + logger.warn("Error ocurred while parsing stream.", e); + } catch (final SAXException e) { + logger.warn("Error ocurred while final parsing stream.", e); + } + return (doc == null) ? null : new NetconfMessage(doc); + } +} diff --git a/opendaylight/netconf/netconf-impl/src/main/resources/getConfig_candidate.xml b/opendaylight/netconf/netconf-impl/src/main/resources/getConfig_candidate.xml new file mode 100644 index 0000000000..3ac18b5cb7 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/resources/getConfig_candidate.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/opendaylight/netconf/netconf-impl/src/main/resources/server_hello.xml b/opendaylight/netconf/netconf-impl/src/main/resources/server_hello.xml new file mode 100644 index 0000000000..6a3f911cd4 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/resources/server_hello.xml @@ -0,0 +1,7 @@ + + + + urn:ietf:params:netconf:base:1.0 + + 1 + diff --git a/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/ConcurrentClientsTest.java b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/ConcurrentClientsTest.java new file mode 100644 index 0000000000..54a3482e34 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/ConcurrentClientsTest.java @@ -0,0 +1,272 @@ +/* + * 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.netconf.impl; + +import com.google.common.base.Optional; +import com.google.common.collect.Sets; +import io.netty.channel.ChannelFuture; +import io.netty.util.HashedWheelTimer; +import org.apache.commons.io.IOUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.opendaylight.controller.config.util.ConfigRegistryJMXClient; +import org.opendaylight.controller.config.util.ConfigTransactionJMXClient; +import org.opendaylight.controller.config.yang.store.api.YangStoreService; +import org.opendaylight.controller.config.yang.store.api.YangStoreSnapshot; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.api.NetconfOperationRouter; +import org.opendaylight.controller.netconf.client.NetconfClient; +import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationServiceFactoryListenerImpl; +import org.opendaylight.controller.netconf.mapping.api.*; +import org.opendaylight.controller.netconf.util.test.XmlFileLoader; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; + +import javax.management.ObjectName; +import javax.net.ssl.SSLContext; +import java.io.DataOutputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.management.ManagementFactory; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +public class ConcurrentClientsTest { + + private static final int CONCURRENCY = 16; + @Mock + private YangStoreService yangStoreService; + @Mock + private ConfigRegistryJMXClient jmxClient; + + private final InetSocketAddress netconfAddress = new InetSocketAddress("127.0.0.1", 8303); + + static final Logger logger = LoggerFactory.getLogger(ConcurrentClientsTest.class); + + private DefaultCommitNotificationProducer commitNot; + private NetconfServerDispatcher dispatch; + + @Before + public void setUp() throws Exception { + { // init mocks + MockitoAnnotations.initMocks(this); + final YangStoreSnapshot yStore = mock(YangStoreSnapshot.class); + doReturn(yStore).when(this.yangStoreService).getYangStoreSnapshot(); + doReturn(Collections.emptyMap()).when(yStore).getModuleMXBeanEntryMap(); + doReturn(Collections.emptyMap()).when(yStore).getModuleMap(); + + final ConfigTransactionJMXClient mockedTCl = mock(ConfigTransactionJMXClient.class); + doReturn(mockedTCl).when(this.jmxClient).getConfigTransactionClient(any(ObjectName.class)); + + doReturn(Collections.emptySet()).when(jmxClient).lookupConfigBeans(); + } + + NetconfOperationServiceFactoryListenerImpl factoriesListener = new NetconfOperationServiceFactoryListenerImpl(); + factoriesListener.onAddNetconfOperationServiceFactory(mockOpF()); + + SessionIdProvider idProvider = new SessionIdProvider(); + NetconfServerSessionNegotiatorFactory serverNegotiatorFactory = new NetconfServerSessionNegotiatorFactory( + new HashedWheelTimer(5000, TimeUnit.MILLISECONDS), factoriesListener, idProvider); + + commitNot = new DefaultCommitNotificationProducer(ManagementFactory.getPlatformMBeanServer()); + + NetconfServerSessionListenerFactory listenerFactory = new NetconfServerSessionListenerFactory( + factoriesListener, commitNot, idProvider); + dispatch = new NetconfServerDispatcher(Optional. absent(), serverNegotiatorFactory, listenerFactory); + + ChannelFuture s = dispatch.createServer(netconfAddress); + s.await(); + } + + private NetconfOperationServiceFactory mockOpF() { + return new NetconfOperationServiceFactory() { + @Override + public NetconfOperationService createService(long netconfSessionId, String netconfSessionIdForReporting) { + return new NetconfOperationService() { + @Override + public Set getCapabilities() { + return Collections.emptySet(); + } + + @Override + public Set getNetconfOperations() { + return Sets. newHashSet(new NetconfOperation() { + @Override + public HandlingPriority canHandle(Document message) { + return HandlingPriority.getHandlingPriority(Integer.MAX_VALUE); + } + + @Override + public Document handle(Document message, NetconfOperationRouter operationRouter) + throws NetconfDocumentedException { + try { + return XmlUtil.readXmlToDocument(""); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + } + + @Override + public Set getFilters() { + return Collections.emptySet(); + } + + @Override + public void close() { + } + }; + } + }; + } + + @After + public void cleanUp() throws Exception { + commitNot.close(); + dispatch.close(); + } + + @Test + public void multipleClients() throws Exception { + List threads = new ArrayList<>(); + + final int attempts = 5; + for (int i = 0; i < CONCURRENCY; i++) { + TestingThread thread = new TestingThread(String.valueOf(i), attempts); + threads.add(thread); + thread.start(); + } + + for (TestingThread thread : threads) { + thread.join(); + assertTrue(thread.success); + } + } + + @Test + public void synchronizationTest() throws Exception { + new BlockingThread("foo").run2(); + } + + @Test + public void multipleBlockingClients() throws Exception { + List threads = new ArrayList<>(); + for (int i = 0; i < CONCURRENCY; i++) { + BlockingThread thread = new BlockingThread(String.valueOf(i)); + threads.add(thread); + thread.start(); + } + + for (BlockingThread thread : threads) { + thread.join(); + assertTrue(thread.success); + } + } + + class BlockingThread extends Thread { + Boolean success; + + public BlockingThread(String name) { + super("client-" + name); + } + + @Override + public void run() { + try { + run2(); + success = true; + } catch (Exception e) { + success = false; + throw new RuntimeException(e); + } + } + + private void run2() throws Exception { + InputStream clientHello = checkNotNull(XmlFileLoader + .getResourceAsStream("netconfMessages/client_hello.xml")); + InputStream getConfig = checkNotNull(XmlFileLoader.getResourceAsStream("netconfMessages/getConfig.xml")); + + Socket clientSocket = new Socket(netconfAddress.getHostString(), netconfAddress.getPort()); + DataOutputStream outToServer = new DataOutputStream(clientSocket.getOutputStream()); + InputStreamReader inFromServer = new InputStreamReader(clientSocket.getInputStream()); + + StringBuffer sb = new StringBuffer(); + while (sb.toString().endsWith("]]>]]>") == false) { + sb.append((char) inFromServer.read()); + } + logger.info(sb.toString()); + + outToServer.write(IOUtils.toByteArray(clientHello)); + outToServer.write("]]>]]>".getBytes()); + outToServer.flush(); + // Thread.sleep(100); + outToServer.write(IOUtils.toByteArray(getConfig)); + outToServer.write("]]>]]>".getBytes()); + outToServer.flush(); + Thread.sleep(100); + sb = new StringBuffer(); + while (sb.toString().endsWith("]]>]]>") == false) { + sb.append((char) inFromServer.read()); + } + logger.info(sb.toString()); + clientSocket.close(); + } + } + + class TestingThread extends Thread { + + private final String clientId; + private final int attempts; + private Boolean success; + + TestingThread(String clientId, int attempts) { + this.clientId = clientId; + this.attempts = attempts; + setName("client-" + clientId); + } + + @Override + public void run() { + try { + final NetconfClient netconfClient = new NetconfClient(clientId, netconfAddress); + long sessionId = netconfClient.getSessionId(); + logger.info("Client with sessionid {} hello exchanged", sessionId); + + final NetconfMessage getMessage = XmlFileLoader + .xmlFileToNetconfMessage("netconfMessages/getConfig.xml"); + NetconfMessage result = netconfClient.sendMessage(getMessage); + logger.info("Client with sessionid {} got result {}", sessionId, result); + netconfClient.close(); + logger.info("Client with session id {} ended", sessionId); + success = true; + } catch (final Exception e) { + success = false; + throw new RuntimeException(e); + } + } + } +} diff --git a/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/MessageHeaderTest.java b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/MessageHeaderTest.java new file mode 100644 index 0000000000..aa3c5d4d9b --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/MessageHeaderTest.java @@ -0,0 +1,40 @@ +/* + * 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.netconf.impl; + +import org.junit.Before; +import org.junit.Test; +import org.opendaylight.controller.netconf.util.messages.NetconfMessageHeader; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +public class MessageHeaderTest { + + private NetconfMessageHeader header = null; + + @Before + public void setUp() { + this.header = new NetconfMessageHeader(); + } + + @Test + public void testFromBytes() { + final byte[] raw = new byte[] { (byte) 0x0a, (byte) 0x23, (byte) 0x35, (byte) 0x38, (byte) 0x0a }; + this.header.fromBytes(raw); + assertEquals(58, this.header.getLength()); + } + + @Test + public void testToBytes() { + this.header.setLength(123); + assertArrayEquals(new byte[] { (byte) 0x0a, (byte) 0x23, (byte) 0x31, (byte) 0x32, (byte) 0x33, (byte) 0x0a }, + this.header.toBytes()); + } +} diff --git a/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/MessageParserTest.java b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/MessageParserTest.java new file mode 100644 index 0000000000..39f45e0c49 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/MessageParserTest.java @@ -0,0 +1,91 @@ +/* + * 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.netconf.impl; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import java.io.IOException; + +import javax.xml.parsers.ParserConfigurationException; + +import org.custommonkey.xmlunit.XMLAssert; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.util.messages.FramingMechanism; +import org.opendaylight.controller.netconf.util.messages.NetconfMessageFactory; +import org.opendaylight.controller.netconf.util.test.XmlFileLoader; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.opendaylight.protocol.framework.DeserializerException; +import org.opendaylight.protocol.framework.DocumentedException; +import org.opendaylight.protocol.util.ByteArray; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +public class MessageParserTest { + + private NetconfMessageFactory parser = null; + + @Before + public void setUp() { + this.parser = new NetconfMessageFactory(); + } + + @Test + public void testPutEOM() throws IOException, SAXException, ParserConfigurationException { + final NetconfMessage msg = XmlFileLoader.xmlFileToNetconfMessage("netconfMessages/client_hello.xml"); + final byte[] bytes = this.parser.put(msg); + assertArrayEquals(NetconfMessageFactory.endOfMessage, ByteArray.subByte(bytes, bytes.length + - NetconfMessageFactory.endOfMessage.length, NetconfMessageFactory.endOfMessage.length)); + } + + @Ignore + @Test + // TODO not working on WINDOWS + // arrays first differed at element [4]; expected:<49> but was:<53> + // at + // org.junit.internal.ComparisonCriteria.arrayEquals(ComparisonCriteria.java:52) + public void testPutChunk() throws IOException, SAXException, ParserConfigurationException { + final NetconfMessage msg = XmlFileLoader.xmlFileToNetconfMessage("netconfMessages/client_hello.xml"); + this.parser.setFramingMechanism(FramingMechanism.CHUNK); + final byte[] bytes = this.parser.put(msg); + final byte[] header = new byte[] { (byte) 0x0a, (byte) 0x23, (byte) 0x32, (byte) 0x31, (byte) 0x31, (byte) 0x0a }; + assertArrayEquals(header, ByteArray.subByte(bytes, 0, header.length)); + assertArrayEquals(NetconfMessageFactory.endOfChunk, ByteArray.subByte(bytes, bytes.length + - NetconfMessageFactory.endOfChunk.length, NetconfMessageFactory.endOfChunk.length)); + } + + @Test + public void testParseEOM() throws IOException, SAXException, DeserializerException, DocumentedException, + ParserConfigurationException { + final Document msg = XmlFileLoader.xmlFileToDocument("netconfMessages/client_hello.xml"); + final byte[] bytes = this.parser.put(new NetconfMessage(msg)); + final Document doc = this.parser + .parse(ByteArray.subByte(bytes, 0, bytes.length - NetconfMessageFactory.endOfMessage.length)) + .iterator().next().getDocument(); + assertEquals(XmlUtil.toString(msg), XmlUtil.toString(doc)); + XMLAssert.assertXMLEqual(msg, doc); + } + + @Test + public void testParseChunk() throws IOException, SAXException, DeserializerException, DocumentedException, + ParserConfigurationException { + final Document msg = XmlFileLoader.xmlFileToDocument("netconfMessages/client_hello.xml"); + this.parser.setFramingMechanism(FramingMechanism.CHUNK); + final byte[] bytes = this.parser.put(new NetconfMessage(msg)); + final Document doc = this.parser + .parse(ByteArray.subByte(bytes, 6, bytes.length - NetconfMessageFactory.endOfChunk.length - 6)) + .iterator().next().getDocument(); + assertEquals(XmlUtil.toString(msg), XmlUtil.toString(doc)); + XMLAssert.assertXMLEqual(msg, doc); + } + +} diff --git a/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/NetconfDispatcherImplTest.java b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/NetconfDispatcherImplTest.java new file mode 100644 index 0000000000..ecbde5904b --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/NetconfDispatcherImplTest.java @@ -0,0 +1,50 @@ +/* + * 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.netconf.impl; + +import java.lang.management.ManagementFactory; +import java.net.InetSocketAddress; + +import javax.net.ssl.SSLContext; + +import org.junit.Test; +import org.opendaylight.controller.netconf.impl.*; +import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationServiceFactoryListener; +import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationServiceFactoryListenerImpl; + +import com.google.common.base.Optional; + +import io.netty.channel.ChannelFuture; +import io.netty.util.HashedWheelTimer; + +public class NetconfDispatcherImplTest { + + @Test + public void test() throws Exception { + + DefaultCommitNotificationProducer commitNot = new DefaultCommitNotificationProducer( + ManagementFactory.getPlatformMBeanServer()); + NetconfOperationServiceFactoryListener factoriesListener = new NetconfOperationServiceFactoryListenerImpl(); + + SessionIdProvider idProvider = new SessionIdProvider(); + NetconfServerSessionNegotiatorFactory serverNegotiatorFactory = new NetconfServerSessionNegotiatorFactory( + new HashedWheelTimer(), factoriesListener, idProvider); + + NetconfServerSessionListenerFactory listenerFactory = new NetconfServerSessionListenerFactory( + factoriesListener, commitNot, idProvider); + NetconfServerDispatcher dispatch = new NetconfServerDispatcher(Optional. absent(), + serverNegotiatorFactory, listenerFactory); + + InetSocketAddress addr = new InetSocketAddress("127.0.0.1", 8333); + ChannelFuture s = dispatch.createServer(addr); + + commitNot.close(); + dispatch.close(); + } +} diff --git a/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/chunked1.txt b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/chunked1.txt new file mode 100644 index 0000000000..aad72393cb --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/chunked1.txt @@ -0,0 +1,35 @@ + +#24 + + + 14 + fred + + < +#2 +/r +#3 +pc> +## diff --git a/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/chunked2.txt b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/chunked2.txt new file mode 100644 index 0000000000..a36a85ea1f --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/chunked2.txt @@ -0,0 +1,48 @@ + +#22 + + + + + +#18 + + + +#19 + + +#8 + + +#77 + + + + + +## diff --git a/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/chunked3.txt b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/chunked3.txt new file mode 100644 index 0000000000..d9dc43d620 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/chunked3.txt @@ -0,0 +1,43 @@ + +#43 + + +#26 + + + +#35 + + + +#39 + + < +#40 +top xmlns="http://example.com/schema/1.2 +#26 +/config"> + + +#36 + + f +#56 +red + + + +#28 + + + + +## diff --git a/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/chunked4.txt b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/chunked4.txt new file mode 100644 index 0000000000..0b8a102dff --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/chunked4.txt @@ -0,0 +1,40 @@ + +#17 + + + + + + +#43 + + + +#16 + + +#22 + + + +## diff --git a/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/chunked5.txt b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/chunked5.txt new file mode 100644 index 0000000000..f8f3c4d7c2 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/chunked5.txt @@ -0,0 +1,42 @@ + +#43 + + + + + < +#4 +/tar +#18 +get> + + +#41 + + + +#29 + Ethernet0/0 + 1500 + +#61 + + + + + +## diff --git a/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/listener/databaseinteractions/jolokia_config_bean_response.txt b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/listener/databaseinteractions/jolokia_config_bean_response.txt new file mode 100644 index 0000000000..2ae32efb65 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/listener/databaseinteractions/jolokia_config_bean_response.txt @@ -0,0 +1,18 @@ +curl http://localhost:17777/jolokia/read/org.opendaylight.controller:instanceName=fixed1,type=ConfigBean,interfaceName=testing-threadpool | jsonpp +{ + "request": { + "mbean": "org.opendaylight.controller:instanceName=fixed1,interfaceName=testing-threadpool,type=ConfigBean", + "type": "read" + }, + "status": 200, + "timestamp": 1362416252, + "value": { + "ExportedInterfaces": [ + "testing-threadpool", + "modifiable-threadpool" + ], + "ImplementationName": "fixed", + "ThreadCount": 10, + "TriggerNewInstanceCreation": false + } +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/listener/databaseinteractions/jolokia_lookupConfigBeans.txt b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/listener/databaseinteractions/jolokia_lookupConfigBeans.txt new file mode 100644 index 0000000000..2ae705a54f --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/listener/databaseinteractions/jolokia_lookupConfigBeans.txt @@ -0,0 +1,18 @@ +$ curl 'http://localhost:17777/jolokia/exec/org.opendaylight.controller:type=ConfigRegistry/lookupConfigBeans()' | jsonpp +{ + "request": { + "mbean": "org.opendaylight.controller:type=ConfigRegistry", + "operation": "lookupConfigBeans()", + "type": "exec" + }, + "status": 200, + "timestamp": 1362417043, + "value": [ + { + "objectName": "org.opendaylight.controller:instanceName=fixed1,interfaceName=modifiable-threadpool,type=ConfigBean" + }, + { + "objectName": "org.opendaylight.controller:instanceName=fixed1,interfaceName=testing-threadpool,type=ConfigBean" + } + ] +} diff --git a/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/notused/client_commit.xml b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/notused/client_commit.xml new file mode 100644 index 0000000000..6eca609b6c --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/notused/client_commit.xml @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/notused/client_lock_candidate.xml b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/notused/client_lock_candidate.xml new file mode 100644 index 0000000000..6a9ed639d8 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/notused/client_lock_candidate.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/notused/client_lock_running.xml b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/notused/client_lock_running.xml new file mode 100644 index 0000000000..2d66c45906 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/notused/client_lock_running.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/notused/client_modify_candidate.xml b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/notused/client_modify_candidate.xml new file mode 100644 index 0000000000..ce67845de1 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/notused/client_modify_candidate.xml @@ -0,0 +1,20 @@ + + + + + + none + test-then-set + stop-on-error + + + + 7 + + + + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/notused/client_unlock_candidate.xml b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/notused/client_unlock_candidate.xml new file mode 100644 index 0000000000..dd6fe1ba1e --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/notused/client_unlock_candidate.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/notused/client_unlock_running.xml b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/notused/client_unlock_running.xml new file mode 100644 index 0000000000..f94af4698d --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/notused/client_unlock_running.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/notused/server_error_missing_attribute.xml b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/notused/server_error_missing_attribute.xml new file mode 100644 index 0000000000..c70184e2b4 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/resources/org/opendaylight/controller/netconf/impl/notused/server_error_missing_attribute.xml @@ -0,0 +1,11 @@ + + + rpc + missing-attribute + error + + message-id + rpc + + + diff --git a/opendaylight/netconf/netconf-impl/src/test/resources/testConfigs/input.json b/opendaylight/netconf/netconf-impl/src/test/resources/testConfigs/input.json new file mode 100644 index 0000000000..5e03681d95 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/resources/testConfigs/input.json @@ -0,0 +1,10 @@ +{ + "value":null, + "status":200, + "request": { + "type":"exec", + "mbean":"java.util.logging:type=Logging", + "operation":"setLoggerLevel", + "arguments":["global","INFO"] + } +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-impl/src/test/resources/testConfigs/input.xml b/opendaylight/netconf/netconf-impl/src/test/resources/testConfigs/input.xml new file mode 100644 index 0000000000..b04ace303f --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/resources/testConfigs/input.xml @@ -0,0 +1,7 @@ + + org.opendaylight.controller:type=AppDeployer + EXEC + lookupConfigBeans + abc,bcd.aas + 64 + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-impl/src/test/resources/testConfigs/inputList.xml b/opendaylight/netconf/netconf-impl/src/test/resources/testConfigs/inputList.xml new file mode 100644 index 0000000000..5fc93532a4 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/resources/testConfigs/inputList.xml @@ -0,0 +1,9 @@ + + org.opendaylight.controller:type=AppDeployer + EXEC + lookupConfigBeans + + 22 + 69 + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-impl/src/test/resources/testConfigs/inputMap.xml b/opendaylight/netconf/netconf-impl/src/test/resources/testConfigs/inputMap.xml new file mode 100644 index 0000000000..9bf5b58dc4 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/resources/testConfigs/inputMap.xml @@ -0,0 +1,14 @@ + + org.opendaylight.controller:type=AppDeployer + EXEC + lookupConfigBeans + + + single + mock + + 2 + 22 + arg + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-impl/src/test/resources/testConfigs/inputMultiple.xml b/opendaylight/netconf/netconf-impl/src/test/resources/testConfigs/inputMultiple.xml new file mode 100644 index 0000000000..23a3489310 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/resources/testConfigs/inputMultiple.xml @@ -0,0 +1,29 @@ + + + org.opendaylight.controller:type=AppDeployer + EXEC + lookupConfigBeans + abc,bcd.aas + 22 + + + org.opendaylight.controller:type=AppDeployer + WRITE + attribute + 22 + + + org.opendaylight.controller:type=AppDeployer + EXEC + lookupConfigBeans + + + single + mock + + 2 + 22 + arg + + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-impl/src/test/resources/testConfigs/inputSetter.xml b/opendaylight/netconf/netconf-impl/src/test/resources/testConfigs/inputSetter.xml new file mode 100644 index 0000000000..abad54d27a --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/resources/testConfigs/inputSetter.xml @@ -0,0 +1,6 @@ + + org.opendaylight.controller:type=AppDeployer + WRITE + attribute + 22 + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-impl/src/test/resources/testConfigs/inputSetterList.xml b/opendaylight/netconf/netconf-impl/src/test/resources/testConfigs/inputSetterList.xml new file mode 100644 index 0000000000..7162b8f056 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/resources/testConfigs/inputSetterList.xml @@ -0,0 +1,8 @@ + + org.opendaylight.controller:type=AppDeployer + WRITE + attribute + 22 + 222 + 223 + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-impl/src/test/resources/testConfigs/inputSetterMap.xml b/opendaylight/netconf/netconf-impl/src/test/resources/testConfigs/inputSetterMap.xml new file mode 100644 index 0000000000..00a0536c0c --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/resources/testConfigs/inputSetterMap.xml @@ -0,0 +1,9 @@ + + org.opendaylight.controller:type=AppDeployer + WRITE + setAtr + + 1 + 2 + + diff --git a/opendaylight/netconf/netconf-impl/src/test/resources/testConfigs/map.json b/opendaylight/netconf/netconf-impl/src/test/resources/testConfigs/map.json new file mode 100644 index 0000000000..60fabb62d1 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/test/resources/testConfigs/map.json @@ -0,0 +1,134 @@ +{ + "timestamp":1362488209, + "status":200, + "request":{ + "mbean":"org.opendaylight.controller:type=ConfigRegistry", + "attribute":"AvailableInterfacesAndImplementations", + "type":"read" + }, + "value":{ + "topology-registry":[ + "single" + ], + "bgp-update":[ + "mock", + "bgp-impl" + ], + "positioning-service":[ + "combine", + "onehop", + "ondemand", + "pxe", + "precompute" + ], + "serializer":[ + "serializer-impl" + ], + "network-topology-factory":[ + "mock-xml", + "bgp-network-topology-factory", + "transient" + ], + "dwe-topology":[ + "ebgp", + "defaultmetric", + "igp", + "network" + ], + "thread-factory":[ + "naming-thread-factory" + ], + "bgp-parser":[ + "parser" + ], + "pcep-dispatcher":[ + "dispatcher" + ], + "threadpool":[ + "flexible", + "fixed", + "scheduled" + ], + "scheduled-threadpool":[ + "scheduled" + ], + "positioning-onehop":[ + "onehop" + ], + "bgp-dispatcher":[ + "bgp-dispatcher-impl" + ], + "cost-combiner":[ + "pxe" + ], + "apsp-provider":[ + "jgrapht", + "parallel", + "single-threaded" + ], + "topology":[ + "ebgp", + "defaultmetric", + "igp", + "network" + ], + "soap-resource":[ + "positioning-adaptor-pxe" + ], + "database-provider-factory":[ + "transient" + ], + "bgp-proposal-checker":[ + "bgp-proposal-checker-impl" + ], + "bgp-proposal":[ + "bgp-proposal-impl" + ], + "listenable-network-topology-factory":[ + "transient" + ], + "event-bus":[ + "sync", + "async" + ], + "topology-registry-provider":[ + "single" + ], + "topology-provider-factory":[ + "transient" + ], + "rest-resource":[ + "topology-resource-holder", + "alto-resource-holder", + "topology-visual-holder", + "network-resource-holder", + "path-resource-holder" + ], + "listenable-database-provider-factory":[ + "transient" + ], + "topology-validator":[ + "accept-all", + "threshold" + ], + "replicator":[ + "replicator-impl" + ], + "server":[ + "soap", + "rest" + ], + "combiner-pxe":[ + "pxe" + ], + "rest":[ + "rest" + ], + "soap":[ + "soap" + ], + "path-service":[ + "cariden" + ] + } +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-it/pom.xml b/opendaylight/netconf/netconf-it/pom.xml new file mode 100644 index 0000000000..a3377d7501 --- /dev/null +++ b/opendaylight/netconf/netconf-it/pom.xml @@ -0,0 +1,165 @@ + + + 4.0.0 + + + netconf-subsystem + org.opendaylight.controller + 0.2.1-SNAPSHOT + + + netconf-it + ${project.artifactId} + + + + + ${project.groupId} + config-api + ${config.version} + test + + + ${project.groupId} + config-util + ${config.version} + test + + + ${project.groupId} + yang-store-api + ${config.version} + test + + + ${project.groupId} + netconf-api + ${project.version} + test + + + org.opendaylight.bgpcep + util + test + + + ${project.groupId} + netconf-client + ${project.version} + test + + + ${project.groupId} + config-netconf-connector + ${project.version} + test + + + ${project.groupId} + yang-test + test + + + ${project.groupId} + config-manager + test + + + ${project.groupId} + config-persister-impl + ${project.version} + test + + + ${project.groupId} + config-manager + ${config.version} + test + test-jar + + + ${project.groupId} + netconf-impl + ${project.version} + test + + + ${project.groupId} + netconf-mapping-api + ${project.version} + test + + + ${project.groupId} + netconf-util + ${project.version} + test + test-jar + + + ${project.groupId} + yang-store-impl + test + + + ${project.groupId} + yang-store-impl + ${config.version} + test + test-jar + + + org.opendaylight.controller + logback-config + 0.2.1-SNAPSHOT + + + + org.slf4j + slf4j-api + + + + org.opendaylight.bgpcep + mockito-configuration + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + 1 + false + false + + + + default-test + + true + + + + integration-tests + integration-test + + test + + + + **/org/opendaylight/controller/netconf/it/*.java + + false + + + + + + + diff --git a/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITTest.java b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITTest.java new file mode 100644 index 0000000000..8a51b7c84d --- /dev/null +++ b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITTest.java @@ -0,0 +1,399 @@ +/* + * 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.netconf.it; + +import com.google.common.base.Optional; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import io.netty.channel.ChannelFuture; +import io.netty.util.HashedWheelTimer; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.opendaylight.controller.config.api.ModuleIdentifier; +import org.opendaylight.controller.config.manager.impl.AbstractConfigTest; +import org.opendaylight.controller.config.manager.impl.factoriesresolver.HardcodedModuleFactoriesResolver; +import org.opendaylight.controller.config.manager.impl.jmx.BaseJMXRegistrator; +import org.opendaylight.controller.config.manager.impl.jmx.RootRuntimeBeanRegistratorImpl; +import org.opendaylight.controller.netconf.persist.impl.ConfigPersisterNotificationHandler; +import org.opendaylight.controller.config.persist.api.Persister; +import org.opendaylight.controller.config.spi.ModuleFactory; +import org.opendaylight.controller.config.util.ConfigTransactionJMXClient; +import org.opendaylight.controller.config.yang.store.api.YangStoreException; +import org.opendaylight.controller.config.yang.store.impl.HardcodedYangStoreService; +import org.opendaylight.controller.config.yang.test.impl.*; +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.client.NetconfClient; +import org.opendaylight.controller.netconf.confignetconfconnector.osgi.NetconfOperationServiceFactoryImpl; +import org.opendaylight.controller.netconf.impl.*; +import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationServiceFactoryListenerImpl; +import org.opendaylight.controller.netconf.util.test.XmlFileLoader; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.opendaylight.protocol.util.SSLUtil; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.xml.sax.SAXException; + +import javax.management.ObjectName; +import javax.net.ssl.SSLContext; +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.io.InputStream; +import java.lang.management.ManagementFactory; +import java.net.InetSocketAddress; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.util.*; +import java.util.concurrent.TimeUnit; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.internal.util.Checks.checkNotNull; + +public class NetconfITTest extends AbstractConfigTest { + + // private static final Logger logger = + // LoggerFactory.getLogger(NetconfITTest.class); + // + private static final InetSocketAddress tcpAddress = new InetSocketAddress("127.0.0.1", 12023); + private static final InetSocketAddress tlsAddress = new InetSocketAddress("127.0.0.1", 12024); + + private NetconfMessage getConfig, getConfigCandidate, editConfig, closeSession; + private DefaultCommitNotificationProducer commitNot; + private NetconfServerDispatcher dispatch, dispatchS; + + @Before + public void setUp() throws Exception { + super.initConfigTransactionManagerImpl(new HardcodedModuleFactoriesResolver(getModuleFactories().toArray( + new ModuleFactory[0]))); + + loadMessages(); + + NetconfOperationServiceFactoryListenerImpl factoriesListener = new NetconfOperationServiceFactoryListenerImpl(); + factoriesListener.onAddNetconfOperationServiceFactory(new NetconfOperationServiceFactoryImpl(getYangStore())); + + commitNot = new DefaultCommitNotificationProducer(ManagementFactory.getPlatformMBeanServer()); + + dispatch = createDispatcher(Optional. absent(), factoriesListener); + ChannelFuture s = dispatch.createServer(tcpAddress); + s.await(); + + dispatchS = createDispatcher(Optional.of(getSslContext()), factoriesListener); + s = dispatchS.createServer(tlsAddress); + s.await(); + } + + private NetconfServerDispatcher createDispatcher(Optional sslC, + NetconfOperationServiceFactoryListenerImpl factoriesListener) { + SessionIdProvider idProvider = new SessionIdProvider(); + NetconfServerSessionNegotiatorFactory serverNegotiatorFactory = new NetconfServerSessionNegotiatorFactory( + new HashedWheelTimer(5000, TimeUnit.MILLISECONDS), factoriesListener, idProvider); + + NetconfServerSessionListenerFactory listenerFactory = new NetconfServerSessionListenerFactory( + factoriesListener, commitNot, idProvider); + + return new NetconfServerDispatcher(sslC, serverNegotiatorFactory, listenerFactory); + } + + @After + public void tearDown() throws Exception { + commitNot.close(); + dispatch.close(); + dispatchS.close(); + } + + private SSLContext getSslContext() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, + IOException, UnrecoverableKeyException, KeyManagementException { + final InputStream keyStore = getClass().getResourceAsStream("/keystore.jks"); + final InputStream trustStore = getClass().getResourceAsStream("/keystore.jks"); + SSLContext sslContext = SSLUtil.initializeSecureContext("password", keyStore, trustStore, "SunX509"); + keyStore.close(); + trustStore.close(); + return sslContext; + } + + private void loadMessages() throws IOException, SAXException, ParserConfigurationException { + this.editConfig = XmlFileLoader.xmlFileToNetconfMessage("netconfMessages/edit_config.xml"); + this.getConfig = XmlFileLoader.xmlFileToNetconfMessage("netconfMessages/getConfig.xml"); + this.getConfigCandidate = XmlFileLoader.xmlFileToNetconfMessage("netconfMessages/getConfig_candidate.xml"); + this.closeSession = XmlFileLoader.xmlFileToNetconfMessage("netconfMessages/closeSession.xml"); + } + + private HardcodedYangStoreService getYangStore() throws YangStoreException, IOException { + final Collection yangDependencies = getBasicYangs(); + return new HardcodedYangStoreService(yangDependencies); + } + + private Collection getBasicYangs() throws IOException { + List paths = Arrays.asList("/META-INF/yang/config.yang", "/META-INF/yang/rpc-context.yang", + "/META-INF/yang/config-test.yang", "/META-INF/yang/config-test-impl.yang", + "/META-INF/yang/ietf-inet-types.yang"); + final Collection yangDependencies = new ArrayList<>(); + for (String path : paths) { + final InputStream is = checkNotNull(getClass().getResourceAsStream(path), path + " not found"); + yangDependencies.add(is); + } + return yangDependencies; + } + + protected List getModuleFactories() { + return getModuleFactoriesS(); + } + + static List getModuleFactoriesS() { + return Lists.newArrayList(new TestImplModuleFactory(), new DepTestImplModuleFactory(), + new NetconfTestImplModuleFactory()); + } + + @Test + public void testTwoSessions() throws Exception { + try (NetconfClient netconfClient = new NetconfClient("1", tcpAddress, 4000)) { + try (NetconfClient netconfClient2 = new NetconfClient("2", tcpAddress, 4000)) { + } + } + } + + @Test(timeout = 10000) + public void testPersister() throws Exception { + Persister persister = mock(Persister.class); + doReturn("mockPersister").when(persister).toString(); + doReturn(Optional.absent()).when(persister).loadLastConfig(); + ConfigPersisterNotificationHandler h = new ConfigPersisterNotificationHandler(persister, tcpAddress, ManagementFactory.getPlatformMBeanServer()); + h.init(); + } + + @Test + public void testSecure() throws Exception { + try (NetconfClient netconfClient = new NetconfClient("1", tlsAddress, 4000, Optional.of(getSslContext()))) { + + } + } + + @Ignore + @Test + public void waitingTest() throws Exception { + final ConfigTransactionJMXClient transaction = this.configRegistryClient.createTransaction(); + transaction.createModule(DepTestImplModuleFactory.NAME, "eb"); + transaction.commit(); + Thread.currentThread().suspend(); + } + + @Test + public void rpcReplyContainsAllAttributesTest() throws Exception { + try (NetconfClient netconfClient = createSession(tcpAddress, "1")) { + final String rpc = "" + + "" + ""; + final Document doc = XmlUtil.readXmlToDocument(rpc); + final NetconfMessage message = netconfClient.sendMessage(new NetconfMessage(doc)); + assertNotNull(message); + final NamedNodeMap expectedAttributes = doc.getDocumentElement().getAttributes(); + final NamedNodeMap returnedAttributes = message.getDocument().getDocumentElement().getAttributes(); + + assertSameAttributes(expectedAttributes, returnedAttributes); + } + } + + private void assertSameAttributes(final NamedNodeMap expectedAttributes, final NamedNodeMap returnedAttributes) { + assertNotNull("Expecting 4 attributes", returnedAttributes); + assertEquals(expectedAttributes.getLength(), returnedAttributes.getLength()); + + for (int i = 0; i < expectedAttributes.getLength(); i++) { + final Node expAttr = expectedAttributes.item(i); + final Node attr = returnedAttributes.item(i); + assertEquals(expAttr.getNodeName(), attr.getNodeName()); + assertEquals(expAttr.getNamespaceURI(), attr.getNamespaceURI()); + assertEquals(expAttr.getTextContent(), attr.getTextContent()); + } + } + + @Test + public void rpcReplyErrorContainsAllAttributesTest() throws Exception { + try (NetconfClient netconfClient = createSession(tcpAddress, "1")) { + final String rpc = "" + + "" + ""; + final Document doc = XmlUtil.readXmlToDocument(rpc); + final NetconfMessage message = netconfClient.sendMessage(new NetconfMessage(doc)); + final NamedNodeMap expectedAttributes = doc.getDocumentElement().getAttributes(); + final NamedNodeMap returnedAttributes = message.getDocument().getDocumentElement().getAttributes(); + + assertSameAttributes(expectedAttributes, returnedAttributes); + } + } + + @Test + public void rpcOutputContainsCorrectNamespace() throws Exception { + final ConfigTransactionJMXClient transaction = this.configRegistryClient.createTransaction(); + ObjectName dep = transaction.createModule(DepTestImplModuleFactory.NAME, "instanceD"); + ObjectName impl = transaction.createModule(NetconfTestImplModuleFactory.NAME, "instance"); + NetconfTestImplModuleMXBean proxy = configRegistryClient + .newMXBeanProxy(impl, NetconfTestImplModuleMXBean.class); + proxy.setTestingDep(dep); + registerRuntimeBean(); + + transaction.commit(); + + try (NetconfClient netconfClient = createSession(tcpAddress, "1")) { + final String expectedNamespace = "urn:opendaylight:params:xml:ns:yang:controller:test:impl"; + + final String rpc = "" + + " " + + "/data/modules/module[name='impl-netconf']/instance[name='instance']" + + "argument1" + "" + ""; + final Document doc = XmlUtil.readXmlToDocument(rpc); + final NetconfMessage message = netconfClient.sendMessage(new NetconfMessage(doc)); + + final Element rpcReply = message.getDocument().getDocumentElement(); + final XmlElement resultElement = XmlElement.fromDomElement(rpcReply).getOnlyChildElement(); + assertEquals("result", resultElement.getName()); + final String namespace = resultElement.getNamespaceAttribute(); + assertEquals(expectedNamespace, namespace); + } + } + + private void registerRuntimeBean() { + BaseJMXRegistrator baseJMXRegistrator = new BaseJMXRegistrator(ManagementFactory.getPlatformMBeanServer()); + RootRuntimeBeanRegistratorImpl runtimeBeanRegistrator = baseJMXRegistrator + .createRuntimeBeanRegistrator(new ModuleIdentifier(NetconfTestImplModuleFactory.NAME, "instance")); + NetconfTestImplRuntimeRegistrator reg = new NetconfTestImplRuntimeRegistrator(runtimeBeanRegistrator); + reg.register(new NetconfTestImplRuntimeMXBean() { + @Override + public Asdf getAsdf() { + return null; + } + + @Override + public Long getCreatedSessions() { + return null; + } + + @Override + public String noArg(String arg1) { + return "from no arg"; + } + }); + } + + @Test + public void testCloseSession() throws Exception { + try (NetconfClient netconfClient = createSession(tcpAddress, "1")) { + + // edit config + Document rpcReply = netconfClient.sendMessage(this.editConfig).getDocument(); + assertIsOK(rpcReply); + + rpcReply = netconfClient.sendMessage(this.closeSession).getDocument(); + + assertIsOK(rpcReply); + } + } + + @Test + public void testEditConfig() throws Exception { + try (NetconfClient netconfClient = createSession(tcpAddress, "1")) { + // send edit_config.xml + final Document rpcReply = netconfClient.sendMessage(this.editConfig).getDocument(); + assertIsOK(rpcReply); + } + } + + @Test + public void testValidate() throws Exception { + try (NetconfClient netconfClient = createSession(tcpAddress, "1")) { + // begin transaction + Document rpcReply = netconfClient.sendMessage(getConfigCandidate).getDocument(); + assertEquals("data", XmlElement.fromDomDocument(rpcReply).getOnlyChildElement().getName()); + + // operations empty + rpcReply = netconfClient.sendMessage(XmlFileLoader.xmlFileToNetconfMessage("netconfMessages/validate.xml")) + .getDocument(); + assertIsOK(rpcReply); + } + } + + private void assertIsOK(final Document rpcReply) { + assertEquals("rpc-reply", rpcReply.getDocumentElement().getTagName()); + assertEquals("ok", XmlElement.fromDomDocument(rpcReply).getOnlyChildElement().getName()); + } + + @Ignore + @Test + // TODO can only send NetconfMessage - it must be valid xml + public void testClientHelloWithAuth() throws Exception { + final String fileName = "netconfMessages/client_hello_with_auth.xml"; + // final InputStream resourceAsStream = + // AbstractListenerTest.class.getResourceAsStream(fileName); + // assertNotNull(resourceAsStream); + try (NetconfClient netconfClient = new NetconfClient("test", tcpAddress, 5000)) { + // IOUtils.copy(resourceAsStream, netconfClient.getStream()); + // netconfClient.getOutputStream().write(NetconfMessageFactory.endOfMessage); + // server should not write anything back + // assertEquals(null, netconfClient.readMessage()); + assertGetConfigWorks(netconfClient); + } + } + + private Document assertGetConfigWorks(final NetconfClient netconfClient) throws InterruptedException { + return assertGetConfigWorks(netconfClient, this.getConfig); + } + + private Document assertGetConfigWorks(final NetconfClient netconfClient, final NetconfMessage getConfigMessage) + throws InterruptedException { + final NetconfMessage rpcReply = netconfClient.sendMessage(getConfigMessage); + assertNotNull(rpcReply); + assertEquals("data", XmlElement.fromDomDocument(rpcReply.getDocument()).getOnlyChildElement().getName()); + return rpcReply.getDocument(); + } + + @Test + public void testGetConfig() throws Exception { + try (NetconfClient netconfClient = createSession(tcpAddress, "1")) { + assertGetConfigWorks(netconfClient); + } + } + + @Test + public void createYangTestBasedOnYuma() throws Exception { + try (NetconfClient netconfClient = createSession(tcpAddress, "1")) { + Document rpcReply = netconfClient.sendMessage( + XmlFileLoader.xmlFileToNetconfMessage("netconfMessages/editConfig_merge_yang-test.xml")) + .getDocument(); + assertEquals("rpc-reply", rpcReply.getDocumentElement().getTagName()); + assertIsOK(rpcReply); + assertGetConfigWorks(netconfClient, this.getConfigCandidate); + rpcReply = netconfClient.sendMessage(XmlFileLoader.xmlFileToNetconfMessage("netconfMessages/commit.xml")) + .getDocument(); + assertIsOK(rpcReply); + + final ObjectName on = new ObjectName( + "org.opendaylight.controller:instanceName=impl-dep-instance,type=Module,moduleFactoryName=impl-dep"); + Set cfgBeans = configRegistryClient.lookupConfigBeans(); + assertEquals(cfgBeans, Sets.newHashSet(on)); + } + } + + private NetconfClient createSession(final InetSocketAddress address, final String expected) throws Exception { + final NetconfClient netconfClient = new NetconfClient("test " + address.toString(), address, 5000); + + assertEquals(expected, Long.toString(netconfClient.getSessionId())); + + return netconfClient; + } + +} diff --git a/opendaylight/netconf/netconf-it/src/test/resources/keystore.jks b/opendaylight/netconf/netconf-it/src/test/resources/keystore.jks new file mode 100644 index 0000000000..201d3758af Binary files /dev/null and b/opendaylight/netconf/netconf-it/src/test/resources/keystore.jks differ diff --git a/opendaylight/netconf/netconf-mapping-api/pom.xml b/opendaylight/netconf/netconf-mapping-api/pom.xml new file mode 100644 index 0000000000..5b6003f185 --- /dev/null +++ b/opendaylight/netconf/netconf-mapping-api/pom.xml @@ -0,0 +1,53 @@ + + + + + netconf-subsystem + org.opendaylight.controller + 0.2.1-SNAPSHOT + + 4.0.0 + netconf-mapping-api + ${project.artifactId} + + bundle + + + + ${project.groupId} + netconf-api + ${project.version} + + + + com.google.guava + guava + + + + + + + + org.apache.felix + maven-bundle-plugin + + + + + + com.google.common.base, + org.opendaylight.controller.netconf.api, + org.w3c.dom + + + org.opendaylight.controller.netconf.mapping.api, + + + + + + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/Capability.java b/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/Capability.java new file mode 100644 index 0000000000..6351c617cf --- /dev/null +++ b/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/Capability.java @@ -0,0 +1,26 @@ +/* + * 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.netconf.mapping.api; + +import com.google.common.base.Optional; + +/** + * Contains capability URI announced by server hello message and optionally its + * corresponding yang schema that can be retrieved by get-schema rpc. + */ +public interface Capability { + + public String getCapabilityUri(); + + public Optional getModuleName(); + + public Optional getRevision(); + + public Optional getCapabilitySchema(); +} diff --git a/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/HandlingPriority.java b/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/HandlingPriority.java new file mode 100644 index 0000000000..5a0688c8d1 --- /dev/null +++ b/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/HandlingPriority.java @@ -0,0 +1,79 @@ +/* + * 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.netconf.mapping.api; + +import com.google.common.base.Optional; + +public class HandlingPriority implements Comparable { + + public static final HandlingPriority CANNOT_HANDLE = new HandlingPriority(); + public static final HandlingPriority HANDLE_WITH_DEFAULT_PRIORITY = new HandlingPriority(Integer.MIN_VALUE); + public static final HandlingPriority HANDLE_WITH_MAX_PRIORITY = new HandlingPriority(Integer.MAX_VALUE); + + private Integer priority; + + public static HandlingPriority getHandlingPriority(int priority) { + return new HandlingPriority(priority); + } + + private HandlingPriority(int priority) { + this.priority = priority; + } + + private HandlingPriority() { + } + + /** + * @return priority number or Optional.absent otherwise + */ + public Optional getPriority() { + return Optional.of(priority).or(Optional. absent()); + } + + // TODO test + + @Override + public int compareTo(HandlingPriority o) { + if (this == o) + return 0; + if (this == CANNOT_HANDLE) + return -1; + if (o == CANNOT_HANDLE) + return 1; + + if (priority > o.priority) + return 1; + if (priority == o.priority) + return 0; + if (priority < o.priority) + return -1; + + throw new IllegalStateException("Unexpected state"); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof HandlingPriority)) + return false; + + HandlingPriority that = (HandlingPriority) o; + + if (priority != null ? !priority.equals(that.priority) : that.priority != null) + return false; + + return true; + } + + @Override + public int hashCode() { + return priority != null ? priority.hashCode() : 0; + } +} diff --git a/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperation.java b/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperation.java new file mode 100644 index 0000000000..58857b4438 --- /dev/null +++ b/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperation.java @@ -0,0 +1,20 @@ +/* + * 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.netconf.mapping.api; + +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfOperationRouter; +import org.w3c.dom.Document; + +public interface NetconfOperation { + + HandlingPriority canHandle(Document message); + + Document handle(Document message, NetconfOperationRouter operationRouter) throws NetconfDocumentedException; +} diff --git a/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationFilter.java b/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationFilter.java new file mode 100644 index 0000000000..22a730726d --- /dev/null +++ b/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationFilter.java @@ -0,0 +1,26 @@ +/* + * 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.netconf.mapping.api; + +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfOperationRouter; +import org.w3c.dom.Document; + +/** + * Filters wrap each netconf operation, if there is one found. Filters are + * sorted and applied from the greatest to smallest sorting order. + */ +public interface NetconfOperationFilter extends Comparable { + + Document doFilter(Document message, NetconfOperationRouter operationRouter, NetconfOperationFilterChain filterChain) + throws NetconfDocumentedException; + + int getSoringOrder(); + +} diff --git a/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationFilterChain.java b/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationFilterChain.java new file mode 100644 index 0000000000..7a0eb9150d --- /dev/null +++ b/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationFilterChain.java @@ -0,0 +1,19 @@ +/* + * 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.netconf.mapping.api; + +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfOperationRouter; +import org.w3c.dom.Document; + +public interface NetconfOperationFilterChain { + + Document execute(Document message, NetconfOperationRouter operationRouter) throws NetconfDocumentedException; + +} diff --git a/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationService.java b/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationService.java new file mode 100644 index 0000000000..858636a389 --- /dev/null +++ b/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationService.java @@ -0,0 +1,36 @@ +/* + * 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.netconf.mapping.api; + +import java.util.Set; + +/** + * + */ +public interface NetconfOperationService extends AutoCloseable { + + /** + * Get capabilities announced by server hello message. + */ + Set getCapabilities(); + + /** + * Get set of netconf operations that are handled by this service. + */ + Set getNetconfOperations(); + + Set getFilters(); + + /** + * Called when netconf session is destroyed. + */ + @Override + void close(); + +} diff --git a/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationServiceFactory.java b/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationServiceFactory.java new file mode 100644 index 0000000000..46b9cd22e0 --- /dev/null +++ b/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationServiceFactory.java @@ -0,0 +1,20 @@ +/* + * 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.netconf.mapping.api; + +/** + * Factory that must be registered in OSGi service registry in order to be used + * by netconf-impl. Responsible for creating per-session instances of + * {@link NetconfOperationService}. + */ +public interface NetconfOperationServiceFactory { + + NetconfOperationService createService(long netconfSessionId, String netconfSessionIdForReporting); + +} diff --git a/opendaylight/netconf/netconf-util/pom.xml b/opendaylight/netconf/netconf-util/pom.xml new file mode 100644 index 0000000000..cb198e7361 --- /dev/null +++ b/opendaylight/netconf/netconf-util/pom.xml @@ -0,0 +1,117 @@ + + 4.0.0 + + + netconf-subsystem + org.opendaylight.controller + 0.2.1-SNAPSHOT + + netconf-util + ${project.artifactId} + bundle + + + + + + ${project.groupId} + netconf-api + ${project.version} + + + ${project.groupId} + netconf-mapping-api + ${project.version} + + + org.opendaylight.controller + config-api + 0.2.1-SNAPSHOT + + + org.opendaylight.bgpcep + framework + + + org.opendaylight.bgpcep + util + + + + org.osgi + org.osgi.core + + + com.google.guava + guava + + + org.slf4j + slf4j-api + + + + + + + org.apache.felix + maven-bundle-plugin + + + + org.opendaylight.controller.netconf.util, + org.opendaylight.controller.netconf.util.xml, + org.opendaylight.controller.netconf.util.osgi, + org.opendaylight.controller.netconf.util.mapping, + org.opendaylight.controller.netconf.util.messages, + + + org.opendaylight.controller.config.stat, + com.google.common.base, + com.google.common.collect, + io.netty.buffer, + io.netty.channel, + io.netty.channel.socket, + io.netty.handler.codec, + io.netty.handler.ssl, + io.netty.util, + io.netty.util.concurrent, + javax.annotation, + javax.net.ssl, + javax.xml.namespace, + javax.xml.parsers, + javax.xml.transform, + javax.xml.transform.dom, + javax.xml.transform.stream, + javax.xml.validation, + javax.xml.xpath, + org.opendaylight.controller.netconf.api, + org.opendaylight.controller.netconf.mapping.api, + org.opendaylight.protocol.framework, + org.opendaylight.protocol.util, + org.osgi.framework, + org.slf4j, + org.w3c.dom, + org.xml.sax, + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + package + + test-jar + + + + + + + + diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/AbstractChannelInitializer.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/AbstractChannelInitializer.java new file mode 100644 index 0000000000..317a126bba --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/AbstractChannelInitializer.java @@ -0,0 +1,76 @@ +/* + * 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.netconf.util; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; + +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.api.NetconfSession; +import org.opendaylight.controller.netconf.util.messages.NetconfMessageFactory; +import org.opendaylight.protocol.framework.ProtocolHandlerFactory; +import org.opendaylight.protocol.framework.ProtocolMessageDecoder; +import org.opendaylight.protocol.framework.ProtocolMessageEncoder; + +import com.google.common.base.Optional; + +import io.netty.channel.ChannelHandler; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.ssl.SslHandler; +import io.netty.util.concurrent.Promise; + +public abstract class AbstractChannelInitializer { + + private final Optional maybeContext; + private final NetconfHandlerFactory handlerFactory; + + public AbstractChannelInitializer(Optional maybeContext) { + this.maybeContext = maybeContext; + this.handlerFactory = new NetconfHandlerFactory(new NetconfMessageFactory()); + } + + public void initialize(SocketChannel ch, Promise promise) { + if (maybeContext.isPresent()) { + initSsl(ch); + } + + ch.pipeline().addLast("frameDecoder", NetconfMessageFactory.getDelimiterFrameDecoder()); + ch.pipeline().addLast(handlerFactory.getDecoders()); + initializeAfterDecoder(ch, promise); + ch.pipeline().addLast(handlerFactory.getEncoders()); + } + + protected abstract void initializeAfterDecoder(SocketChannel ch, Promise promise); + + private void initSsl(SocketChannel ch) { + SSLEngine sslEngine = maybeContext.get().createSSLEngine(); + initSslEngine(sslEngine); + final SslHandler handler = new SslHandler(sslEngine); + ch.pipeline().addLast("ssl", handler); + } + + protected abstract void initSslEngine(SSLEngine sslEngine); + + private static final class NetconfHandlerFactory extends ProtocolHandlerFactory { + + public NetconfHandlerFactory(final NetconfMessageFactory msgFactory) { + super(msgFactory); + } + + @Override + public ChannelHandler[] getEncoders() { + return new ChannelHandler[] { new ProtocolMessageEncoder(this.msgFactory) }; + } + + @Override + public ChannelHandler[] getDecoders() { + return new ChannelHandler[] { new ProtocolMessageDecoder(this.msgFactory) }; + } + } +} diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/AbstractNetconfSessionNegotiator.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/AbstractNetconfSessionNegotiator.java new file mode 100644 index 0000000000..9069d85d88 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/AbstractNetconfSessionNegotiator.java @@ -0,0 +1,157 @@ +/* + * 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.netconf.util; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import io.netty.channel.Channel; +import io.netty.handler.ssl.SslHandler; +import io.netty.util.Timeout; +import io.netty.util.Timer; +import io.netty.util.TimerTask; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; +import io.netty.util.concurrent.Promise; +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.api.NetconfSession; +import org.opendaylight.controller.netconf.api.NetconfSessionPreferences; +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.protocol.framework.AbstractSessionNegotiator; +import org.opendaylight.protocol.framework.SessionListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; + +import java.util.concurrent.TimeUnit; + +public abstract class AbstractNetconfSessionNegotiator

+ extends AbstractSessionNegotiator { + + // TODO what time ? + private static final long INITIAL_HOLDTIMER = 1; + private static final Logger logger = LoggerFactory.getLogger(AbstractNetconfSessionNegotiator.class); + + protected final P sessionPreferences; + + private final SessionListener sessionListener; + + /** + * Possible states for Finite State Machine + */ + private enum State { + IDLE, OPEN_WAIT, FAILED, ESTABLISHED + } + + private State state = State.IDLE; + private final Timer timer; + + protected AbstractNetconfSessionNegotiator(P sessionPreferences, Promise promise, Channel channel, Timer timer, + SessionListener sessionListener) { + super(promise, channel); + this.sessionPreferences = sessionPreferences; + this.timer = timer; + this.sessionListener = sessionListener; + } + + @Override + protected void startNegotiation() throws Exception { + final Optional sslHandler = getSslHandler(channel); + if (sslHandler.isPresent()) { + Future future = sslHandler.get().handshakeFuture(); + future.addListener(new GenericFutureListener>() { + @Override + public void operationComplete(Future future) throws Exception { + Preconditions.checkState(future.isSuccess(), "Ssl handshake was not successful"); + logger.debug("Ssl handshake complete"); + start(); + } + }); + } else + start(); + } + + private static Optional getSslHandler(Channel channel) { + final SslHandler sslHandler = channel.pipeline().get(SslHandler.class); + return sslHandler == null ? Optional. absent() : Optional.of(sslHandler); + } + + private void start() { + final NetconfMessage helloMessage = this.sessionPreferences.getHelloMessage(); + logger.debug("Session negotiation started with hello message {}", XmlUtil.toString(helloMessage.getDocument())); + + sendMessage(helloMessage); + changeState(State.OPEN_WAIT); + + this.timer.newTimeout(new TimerTask() { + @Override + public void run(final Timeout timeout) throws Exception { + synchronized (this) { + if (state != State.ESTABLISHED) { + final IllegalStateException cause = new IllegalStateException( + "Session was not established after " + timeout); + negotiationFailed(cause); + changeState(State.FAILED); + } + } + } + }, INITIAL_HOLDTIMER, TimeUnit.MINUTES); + } + + private void sendMessage(NetconfMessage message) { + this.channel.writeAndFlush(message); + } + + @Override + protected void handleMessage(NetconfMessage netconfMessage) { + final Document doc = netconfMessage.getDocument(); + + if (isHelloMessage(doc)) { + changeState(State.ESTABLISHED); + S session = getSession(sessionListener, channel, doc); + negotiationSuccessful(session); + } else { + final IllegalStateException cause = new IllegalStateException( + "Received message was not hello as expected, but was " + XmlUtil.toString(doc)); + negotiationFailed(cause); + } + } + + protected abstract S getSession(SessionListener sessionListener, Channel channel, Document doc); + + private boolean isHelloMessage(Document doc) { + try { + XmlElement.fromDomElementWithExpected(doc.getDocumentElement(), "hello", + XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0); + + } catch (IllegalArgumentException | IllegalStateException e) { + return false; + } + return true; + } + + private void changeState(final State newState) { + logger.debug("Changing state from : {} to : {}", state, newState); + Preconditions.checkState(isStateChangePermitted(state, newState), "Cannot change state from %s to %s", state, + newState); + this.state = newState; + } + + private static boolean isStateChangePermitted(State state, State newState) { + if (state == State.IDLE && newState == State.OPEN_WAIT) + return true; + if (state == State.OPEN_WAIT && newState == State.ESTABLISHED) + return true; + if (state == State.OPEN_WAIT && newState == State.FAILED) + return true; + + return false; + } +} diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/mapping/AbstractNetconfOperation.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/mapping/AbstractNetconfOperation.java new file mode 100644 index 0000000000..5850e64a05 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/mapping/AbstractNetconfOperation.java @@ -0,0 +1,99 @@ +/* + * 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.netconf.util.mapping; + +import java.util.Map; + +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfOperationRouter; +import org.opendaylight.controller.netconf.mapping.api.HandlingPriority; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperation; +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.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public abstract class AbstractNetconfOperation implements NetconfOperation { + private final String netconfSessionIdForReporting; + + protected AbstractNetconfOperation(String netconfSessionIdForReporting) { + this.netconfSessionIdForReporting = netconfSessionIdForReporting; + } + + public String getNetconfSessionIdForReporting() { + return netconfSessionIdForReporting; + } + + @Override + public HandlingPriority canHandle(Document message) { + OperationNameAndNamespace operationNameAndNamespace = new OperationNameAndNamespace(message); + return canHandle(operationNameAndNamespace.getOperationName(), operationNameAndNamespace.getNamespace()); + } + + public static class OperationNameAndNamespace { + private final String operationName, namespace; + + public OperationNameAndNamespace(Document message) { + XmlElement requestElement = getRequestElementWithCheck(message); + + XmlElement operationElement = requestElement.getOnlyChildElement(); + operationName = operationElement.getName(); + namespace = operationElement.getNamespace(); + } + + public String getOperationName() { + return operationName; + } + + public String getNamespace() { + return namespace; + } + } + + protected static XmlElement getRequestElementWithCheck(Document message) { + return XmlElement.fromDomElementWithExpected(message.getDocumentElement(), XmlNetconfConstants.RPC_KEY, + XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0); + } + + protected abstract HandlingPriority canHandle(String operationName, String netconfOperationNamespace); + + @Override + public Document handle(Document message, NetconfOperationRouter opRouter) throws NetconfDocumentedException { + + XmlElement requestElement = getRequestElementWithCheck(message); + + Document document = XmlUtil.newDocument(); + + XmlElement operationElement = requestElement.getOnlyChildElement(); + Map attributes = requestElement.getAttributes(); + + Element response = handle(document, operationElement, opRouter); + + Element rpcReply = document.createElementNS(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, + XmlNetconfConstants.RPC_REPLY_KEY); + rpcReply.appendChild(response); + + for (String attrName : attributes.keySet()) { + rpcReply.setAttribute(attrName, attributes.get(attrName).getNodeValue()); + } + + document.appendChild(rpcReply); + return document; + } + + protected abstract Element handle(Document document, XmlElement operationElement, NetconfOperationRouter opRouter) + throws NetconfDocumentedException; + + @Override + public String toString() { + return getClass() + "{" + netconfSessionIdForReporting + '}'; + } +} diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/FramingMechanism.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/FramingMechanism.java new file mode 100644 index 0000000000..c118ca81ef --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/FramingMechanism.java @@ -0,0 +1,26 @@ +/* + * 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.netconf.util.messages; + +/** + * Known NETCONF framing mechanisms. + */ +public enum FramingMechanism { + /** + * @see Chunked + * framing mechanism + */ + CHUNK, + /** + * @see End-of-message + * framing mechanism + */ + EOM +} diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/NetconfMessageFactory.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/NetconfMessageFactory.java new file mode 100644 index 0000000000..ca3079bb16 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/NetconfMessageFactory.java @@ -0,0 +1,141 @@ +/* + * 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.netconf.util.messages; + +import com.google.common.base.Charsets; +import com.google.common.base.Optional; +import com.google.common.collect.Lists; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandler; +import io.netty.handler.codec.DelimiterBasedFrameDecoder; +import org.opendaylight.controller.netconf.api.NetconfDeserializerException; +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.opendaylight.protocol.framework.DeserializerException; +import org.opendaylight.protocol.framework.DocumentedException; +import org.opendaylight.protocol.framework.ProtocolMessageFactory; +import org.opendaylight.protocol.util.ByteArray; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Comment; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.List; + +/** + * NetconfMessageFactory for (de)serializing DOM documents. + */ +public final class NetconfMessageFactory implements ProtocolMessageFactory { + + private static final Logger logger = LoggerFactory.getLogger(NetconfMessageFactory.class); + + public static final byte[] endOfMessage = "]]>]]>".getBytes(Charsets.UTF_8); + + public static final byte[] endOfChunk = "\n##\n".getBytes(Charsets.UTF_8); + + private static final int MAX_CHUNK_SIZE = 1024; // Bytes + + private FramingMechanism framing = FramingMechanism.EOM; + + private final Optional clientId; + + public NetconfMessageFactory() { + clientId = Optional.absent(); + } + + public NetconfMessageFactory(Optional clientId) { + this.clientId = clientId; + } + + public static ChannelHandler getDelimiterFrameDecoder() { + return new DelimiterBasedFrameDecoder(Integer.MAX_VALUE, Unpooled.copiedBuffer(endOfMessage)); + } + + @Override + public List parse(byte[] bytes) throws DeserializerException, DocumentedException { + String s = Charsets.UTF_8.decode(ByteBuffer.wrap(bytes)).toString(); + logger.debug("Parsing message \n{}", s); + if (bytes[0] == '[') { + // yuma sends auth information in the first line. Ignore until ]\n + // is found. + int endOfAuthHeader = ByteArray.findByteSequence(bytes, new byte[] { ']', '\n' }); + if (endOfAuthHeader > -1) { + bytes = Arrays.copyOfRange(bytes, endOfAuthHeader + 2, bytes.length); + } + } + List messages = Lists.newArrayList(); + try { + Document doc = XmlUtil.readXmlToDocument(new ByteArrayInputStream(bytes)); + messages.add(new NetconfMessage(doc)); + } catch (final SAXException | IOException | IllegalStateException e) { + throw new NetconfDeserializerException("Could not parse message from " + new String(bytes), e); + } + return messages; + } + + @Override + public byte[] put(NetconfMessage netconfMessage) { + if (clientId.isPresent()) { + Comment comment = netconfMessage.getDocument().createComment("clientId:" + clientId.get()); + netconfMessage.getDocument().appendChild(comment); + } + byte[] bytes = (this.framing == FramingMechanism.EOM) ? this.putEOM(netconfMessage) : this + .putChunked(netconfMessage); + String content = xmlToString(netconfMessage.getDocument()); + + logger.trace("Putting message \n{}", content); + return bytes; + } + + private byte[] putEOM(NetconfMessage msg) { + // create byte buffer from the String XML + // all Netconf messages are encoded using UTF-8 + final ByteBuffer msgBytes = Charsets.UTF_8.encode(xmlToString(msg.getDocument())); + final ByteBuffer result = ByteBuffer.allocate(msgBytes.limit() + endOfMessage.length); + result.put(msgBytes); + // put end of message + result.put(endOfMessage); + return result.array(); + } + + private byte[] putChunked(NetconfMessage msg) { + final ByteBuffer msgBytes = Charsets.UTF_8.encode(xmlToString(msg.getDocument())); + final NetconfMessageHeader h = new NetconfMessageHeader(); + if (msgBytes.limit() > MAX_CHUNK_SIZE) + logger.warn("Netconf message too long, should be split."); + h.setLength(msgBytes.limit()); + final byte[] headerBytes = h.toBytes(); + final ByteBuffer result = ByteBuffer.allocate(headerBytes.length + msgBytes.limit() + endOfChunk.length); + result.put(headerBytes); + result.put(msgBytes); + result.put(endOfChunk); + return result.array(); + } + + private String xmlToString(Document doc) { + return XmlUtil.toString(doc, false); + } + + /** + * For Hello message the framing is always EOM, but the framing mechanism + * may change. + * + * @param fm + * new framing mechanism + */ + public void setFramingMechanism(final FramingMechanism fm) { + logger.debug("Framing mechanism changed to {}", fm); + this.framing = fm; + } +} diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/NetconfMessageHeader.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/NetconfMessageHeader.java new file mode 100644 index 0000000000..ccf7a3fbd2 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/NetconfMessageHeader.java @@ -0,0 +1,85 @@ +/* + * 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.netconf.util.messages; + +import com.google.common.base.Charsets; +import com.google.common.base.Preconditions; +import org.opendaylight.protocol.util.ByteArray; + +import java.nio.ByteBuffer; + +/** + * Netconf message header is used only when chunked framing mechanism is + * supported. The header consists of only the length field. + */ +public final class NetconfMessageHeader { + + private long length; + + // \n#\n + private static final byte[] headerBegin = new byte[] { (byte) 0x0a, (byte) 0x23 }; + + private static final byte headerEnd = (byte) 0x0a; + + public static final int MIN_HEADER_LENGTH = 4; // bytes + + public static final int MAX_HEADER_LENGTH = 13; // bytes + + private boolean parsed = false; + + public NetconfMessageHeader() { + + } + + public NetconfMessageHeader fromBytes(final byte[] bytes) { + // the length is variable therefore bytes between headerBegin and + // headerEnd mark the length + // the length should be only numbers and therefore easily parsed with + // ASCII + this.length = Long.parseLong(Charsets.US_ASCII.decode( + ByteBuffer.wrap(ByteArray.subByte(bytes, headerBegin.length, bytes.length - headerBegin.length - 1))) + .toString()); + Preconditions.checkState(this.length < Integer.MAX_VALUE && this.length > 0); + this.parsed = true; + return this; + } + + public byte[] toBytes() { + final byte[] l = String.valueOf(this.length).getBytes(Charsets.US_ASCII); + final byte[] h = new byte[headerBegin.length + l.length + 1]; + System.arraycopy(headerBegin, 0, h, 0, headerBegin.length); + System.arraycopy(l, 0, h, headerBegin.length, l.length); + System.arraycopy(new byte[] { headerEnd }, 0, h, headerBegin.length + l.length, 1); + return h; + } + + // FIXME: improve precision to long + public int getLength() { + return (int) this.length; + } + + public void setLength(final int length) { + this.length = length; + } + + /** + * @return the parsed + */ + public boolean isParsed() { + return this.parsed; + } + + /** + * @param parsed + * the parsed to set + */ + public void setParsed() { + this.parsed = false; + } +} diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/NetconfMessageUtil.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/NetconfMessageUtil.java new file mode 100644 index 0000000000..46053e734e --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/NetconfMessageUtil.java @@ -0,0 +1,42 @@ +/* + * 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.netconf.util.messages; + +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; +import org.w3c.dom.Document; + +public class NetconfMessageUtil { + + public static boolean isOKMessage(NetconfMessage message) { + return isOKMessage(message.getDocument()); + } + + public static boolean isOKMessage(Document document) { + return isOKMessage(XmlElement.fromDomDocument(document)); + } + + public static boolean isOKMessage(XmlElement xmlElement) { + return xmlElement.getOnlyChildElement().getName().equals(XmlNetconfConstants.OK); + } + + public static boolean isErrorMEssage(NetconfMessage message) { + return isErrorMessage(message.getDocument()); + } + + public static boolean isErrorMessage(Document document) { + return isErrorMessage(XmlElement.fromDomDocument(document)); + } + + public static boolean isErrorMessage(XmlElement xmlElement) { + return xmlElement.getOnlyChildElement().getName().equals(XmlNetconfConstants.RPC_ERROR); + + } +} diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/SendErrorExceptionUtil.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/SendErrorExceptionUtil.java new file mode 100644 index 0000000000..639b428e09 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/messages/SendErrorExceptionUtil.java @@ -0,0 +1,129 @@ +/* + * 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.netconf.util.messages; + +import com.google.common.base.Preconditions; +import io.netty.channel.Channel; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.api.NetconfSession; +import org.opendaylight.controller.netconf.util.xml.XMLNetconfUtil; +import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.*; + +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import java.io.InputStream; +import java.util.Map.Entry; + +public class SendErrorExceptionUtil { + private static final Logger logger = LoggerFactory.getLogger(SendErrorExceptionUtil.class); + + public static void sendErrorMessage(final NetconfSession session, + final NetconfDocumentedException sendErrorException) { + logger.info("Sending error {}", sendErrorException.getMessage(), sendErrorException); + final Document errorDocument = createDocument(sendErrorException); + session.sendMessage(new NetconfMessage(errorDocument)); + } + + public static void sendErrorMessage(Channel channel, NetconfDocumentedException sendErrorException) { + logger.info("Sending error {}", sendErrorException.getMessage(), sendErrorException); + final Document errorDocument = createDocument(sendErrorException); + channel.writeAndFlush(new NetconfMessage(errorDocument)); + } + + public static void sendErrorMessage(NetconfSession session, NetconfDocumentedException sendErrorException, + NetconfMessage incommingMessage) { + final Document errorDocument = createDocument(sendErrorException); + logger.info("Sending error {}", XmlUtil.toString(errorDocument)); + tryToCopyAttributes(incommingMessage.getDocument(), errorDocument, sendErrorException); + session.sendMessage(new NetconfMessage(errorDocument)); + } + + private static void tryToCopyAttributes(final Document incommingDocument, final Document errorDocument, + final NetconfDocumentedException sendErrorException) { + try { + final Element incommingRpc = incommingDocument.getDocumentElement(); + Preconditions.checkState(incommingRpc.getTagName().equals(XmlNetconfConstants.RPC_KEY), "Missing " + + XmlNetconfConstants.RPC_KEY + " " + "element"); + + final Element rpcReply = errorDocument.getDocumentElement(); + Preconditions.checkState(rpcReply.getTagName().equals(XmlNetconfConstants.RPC_REPLY_KEY), "Missing " + + XmlNetconfConstants.RPC_REPLY_KEY + " element"); + + final NamedNodeMap incomingAttributes = incommingRpc.getAttributes(); + for (int i = 0; i < incomingAttributes.getLength(); i++) { + final Attr attr = (Attr) incomingAttributes.item(i); + // skip namespace + if (attr.getNodeName().equals(XmlUtil.XMLNS_ATTRIBUTE_KEY)) + continue; + rpcReply.setAttributeNode((Attr) errorDocument.importNode(attr, true)); + } + } catch (final Exception e) { + logger.warn("Unable to copy incomming attributes to {}, returned rpc-error might be invalid for client", + sendErrorException, e); + } + } + + private static XPathExpression rpcErrorExpression = XMLNetconfUtil + .compileXPath("/netconf:rpc-reply/netconf:rpc-error"); + private static XPathExpression errorTypeExpression = XMLNetconfUtil.compileXPath("netconf:error-type"); + private static XPathExpression errorTagExpression = XMLNetconfUtil.compileXPath("netconf:error-tag"); + private static XPathExpression errorSeverityExpression = XMLNetconfUtil.compileXPath("netconf:error-severity"); + + private static Document createDocument(final NetconfDocumentedException sendErrorException) { + + final InputStream errIS = SendErrorExceptionUtil.class.getResourceAsStream("server_error.xml"); + Document originalErrorDocument; + try { + originalErrorDocument = XmlUtil.readXmlToDocument(errIS); + } catch (final Exception e) { + throw new IllegalStateException(e); + } + + final Document errorDocument = XmlUtil.createDocumentCopy(originalErrorDocument); + final Node rootNode = errorDocument.getFirstChild(); + + final Node rpcErrorNode = (Node) XmlUtil.evaluateXPath(rpcErrorExpression, rootNode, XPathConstants.NODE); + + final Node errorTypeNode = (Node) XmlUtil.evaluateXPath(errorTypeExpression, rpcErrorNode, XPathConstants.NODE); + errorTypeNode.setTextContent(sendErrorException.getErrorType().getTagValue()); + + final Node errorTagNode = (Node) XmlUtil.evaluateXPath(errorTagExpression, rpcErrorNode, XPathConstants.NODE); + errorTagNode.setTextContent(sendErrorException.getErrorTag().getTagValue()); + + final Node errorSeverityNode = (Node) XmlUtil.evaluateXPath(errorSeverityExpression, rpcErrorNode, + XPathConstants.NODE); + errorSeverityNode.setTextContent(sendErrorException.getErrorSeverity().getTagValue()); + + if (sendErrorException.getErrorInfo() != null && sendErrorException.getErrorInfo().isEmpty() == false) { + /* + * message-id + * rpc + */ + final Node errorInfoNode = errorDocument.createElementNS( + XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, "error-info"); + + errorInfoNode.setPrefix(rootNode.getPrefix()); + rpcErrorNode.appendChild(errorInfoNode); + for (final Entry errorInfoEntry : sendErrorException.getErrorInfo().entrySet()) { + final Node node = errorDocument.createElementNS( + XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, errorInfoEntry.getKey()); + node.setTextContent(errorInfoEntry.getValue()); + errorInfoNode.appendChild(node); + } + + } + return errorDocument; + } + +} diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/osgi/NetconfConfigUtil.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/osgi/NetconfConfigUtil.java new file mode 100644 index 0000000000..5c9d823cc0 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/osgi/NetconfConfigUtil.java @@ -0,0 +1,124 @@ +/* + * 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.netconf.util.osgi; + +import com.google.common.base.Optional; +import org.opendaylight.controller.config.stat.ConfigProvider; +import org.opendaylight.protocol.util.SSLUtil; + +import javax.net.ssl.SSLContext; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.net.InetSocketAddress; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +public class NetconfConfigUtil { + private static final String PREFIX_PROP = "netconf."; + + private enum InfixProp { + tcp, tls + } + + private static final String PORT_SUFFIX_PROP = ".port"; + private static final String ADDRESS_SUFFIX_PROP = ".address"; + + private static final String NETCONF_TLS_KEYSTORE_PROP = PREFIX_PROP + InfixProp.tls + ".keystore"; + private static final String NETCONF_TLS_KEYSTORE_PASSWORD_PROP = NETCONF_TLS_KEYSTORE_PROP + ".password"; + + public static Optional extractTCPNetconfAddress(ConfigProvider configProvider) { + return extractSomeNetconfAddress(configProvider, InfixProp.tcp); + } + + public static Optional extractTLSConfiguration(ConfigProvider configProvider) { + Optional address = extractSomeNetconfAddress(configProvider, InfixProp.tls); + if (address.isPresent()) { + String keystoreFileName = configProvider.getProperty(NETCONF_TLS_KEYSTORE_PROP); + File keystoreFile = new File(keystoreFileName); + checkState(keystoreFile.exists() && keystoreFile.isFile() && keystoreFile.canRead(), + "Keystore file %s does not exist or is not readable file", keystoreFileName); + keystoreFile = keystoreFile.getAbsoluteFile(); + String keystorePassword = configProvider.getProperty(NETCONF_TLS_KEYSTORE_PASSWORD_PROP); + checkNotNull(keystoreFileName, "Property %s must be defined for tls netconf server", + NETCONF_TLS_KEYSTORE_PROP); + keystorePassword = keystorePassword != null ? keystorePassword : ""; + return Optional.of(new TLSConfiguration(address.get(), keystoreFile, keystorePassword)); + } else { + return Optional.absent(); + } + } + + public static class TLSConfiguration { + private final InetSocketAddress address; + private final File keystoreFile; + private final String keystorePassword; + private final SSLContext sslContext; + + TLSConfiguration(InetSocketAddress address, File keystoreFile, String keystorePassword) { + this.address = address; + this.keystoreFile = keystoreFile; + this.keystorePassword = keystorePassword; + try { + try (InputStream keyStoreIS = new FileInputStream(keystoreFile)) { + try (InputStream trustStoreIS = new FileInputStream(keystoreFile)) { + sslContext = SSLUtil.initializeSecureContext("password", keyStoreIS, trustStoreIS, "SunX509"); + } + } + } catch (Exception e) { + throw new RuntimeException("Cannot initialize ssl context for netconf file " + keystoreFile, e); + } + } + + public SSLContext getSslContext() { + return sslContext; + } + + public InetSocketAddress getAddress() { + return address; + } + + public File getKeystoreFile() { + return keystoreFile; + } + + public String getKeystorePassword() { + return keystorePassword; + } + } + + /** + * @param configProvider + * from which properties are being read. + * @param infixProp + * either tcp or tls + * @return absent if address is missing, value if address and port are + * valid. + * @throws IllegalStateException + * if address or port are invalid + */ + private static Optional extractSomeNetconfAddress(ConfigProvider configProvider, + InfixProp infixProp) { + String address = configProvider.getProperty(PREFIX_PROP + infixProp + ADDRESS_SUFFIX_PROP); + if (address == null) { + return Optional.absent(); + } + String portKey = PREFIX_PROP + infixProp + PORT_SUFFIX_PROP; + String portString = configProvider.getProperty(portKey); + checkNotNull(portString, "Netconf port must be specified in properties file with " + portKey); + try { + int port = Integer.valueOf(portString); + return Optional.of(new InetSocketAddress(address, port)); + } catch (RuntimeException e) { + throw new IllegalStateException("Cannot create " + infixProp + " netconf address from address:" + address + + " and port:" + portString, e); + } + } +} diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/HardcodedNamespaceResolver.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/HardcodedNamespaceResolver.java new file mode 100644 index 0000000000..23fe7cdf41 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/HardcodedNamespaceResolver.java @@ -0,0 +1,59 @@ +/* + * 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.netconf.util.xml; + +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; + +import javax.xml.namespace.NamespaceContext; + +import com.google.common.collect.ImmutableMap; + +// http://www.ibm.com/developerworks/library/x-nmspccontext/ +public class HardcodedNamespaceResolver implements NamespaceContext { + private final Map prefixesToNamespaces; + + public HardcodedNamespaceResolver(String prefix, String namespace) { + this(ImmutableMap.of(prefix, namespace)); + } + + public HardcodedNamespaceResolver(Map prefixesToNamespaces) { + this.prefixesToNamespaces = Collections.unmodifiableMap(prefixesToNamespaces); + } + + /** + * This method returns the uri for all prefixes needed. Wherever possible it + * uses XMLConstants. + * + * @param prefix + * @return uri + */ + @Override + public String getNamespaceURI(String prefix) { + if (prefixesToNamespaces.containsKey(prefix)) { + return prefixesToNamespaces.get(prefix); + } else { + throw new IllegalStateException("Prefix mapping not found for " + prefix); + } + } + + @Override + public String getPrefix(String namespaceURI) { + // Not needed in this context. + return null; + } + + @Override + public Iterator getPrefixes(String namespaceURI) { + // Not needed in this context. + return null; + } + +} diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/XMLNetconfUtil.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/XMLNetconfUtil.java new file mode 100644 index 0000000000..d9303228f9 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/XMLNetconfUtil.java @@ -0,0 +1,30 @@ +/* + * 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.netconf.util.xml; + +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +public class XMLNetconfUtil { + + public static XPathExpression compileXPath(String xPath) { + final XPathFactory xPathfactory = XPathFactory.newInstance(); + final XPath xpath = xPathfactory.newXPath(); + xpath.setNamespaceContext(new HardcodedNamespaceResolver("netconf", + XmlNetconfConstants.RFC4741_TARGET_NAMESPACE)); + try { + return xpath.compile(xPath); + } catch (final XPathExpressionException e) { + throw new IllegalStateException("Error while compiling xpath expression " + xPath, e); + } + } + +} diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/XmlElement.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/XmlElement.java new file mode 100644 index 0000000000..5a7fde4e3d --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/XmlElement.java @@ -0,0 +1,367 @@ +/* + * 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.netconf.util.xml; + +import java.io.IOException; +import java.util.*; + +import javax.annotation.Nullable; + +import org.w3c.dom.*; +import org.xml.sax.SAXException; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.collect.Collections2; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +public class XmlElement { + + public final Element element; + + private XmlElement(Element element) { + this.element = element; + } + + public static XmlElement fromDomElement(Element e) { + return new XmlElement(e); + } + + public static XmlElement fromDomDocument(Document xml) { + return new XmlElement(xml.getDocumentElement()); + } + + public static XmlElement fromString(String s) { + try { + return new XmlElement(XmlUtil.readXmlToElement(s)); + } catch (IOException | SAXException e) { + throw new IllegalArgumentException("Unable to create from " + s, e); + } + } + + public static XmlElement fromDomElementWithExpected(Element element, String expectedName) { + XmlElement xmlElement = XmlElement.fromDomElement(element); + xmlElement.checkName(expectedName); + return xmlElement; + } + + public static XmlElement fromDomElementWithExpected(Element element, String expectedName, String expectedNamespace) { + XmlElement xmlElement = XmlElement.fromDomElementWithExpected(element, expectedName); + xmlElement.checkNamespace(expectedNamespace); + return xmlElement; + } + + private static Map extractNamespaces(Element typeElement) { + Map namespaces = new HashMap<>(); + NamedNodeMap attributes = typeElement.getAttributes(); + for (int i = 0; i < attributes.getLength(); i++) { + Node attribute = attributes.item(i); + String attribKey = attribute.getNodeName(); + if (attribKey.startsWith(XmlUtil.XMLNS_ATTRIBUTE_KEY)) { + String prefix; + if (attribKey.equals(XmlUtil.XMLNS_ATTRIBUTE_KEY)) { + prefix = ""; + } else { + Preconditions.checkState(attribKey.startsWith(XmlUtil.XMLNS_ATTRIBUTE_KEY + ":")); + prefix = attribKey.substring(XmlUtil.XMLNS_ATTRIBUTE_KEY.length() + 1); + } + namespaces.put(prefix, attribute.getNodeValue()); + } + } + return namespaces; + } + + public void checkName(String expectedName) { + Preconditions.checkArgument(getName().equals(expectedName), "Expected %s xml element but was %s", expectedName, + getName()); + } + + public void checkNamespaceAttribute(String expectedNamespace) { + Preconditions.checkArgument(getNamespaceAttribute().equals(expectedNamespace), + "Unexpected namespace %s for element %s, should be %s", getNamespaceAttribute(), expectedNamespace); + } + + public void checkNamespace(String expectedNamespace) { + Preconditions.checkArgument(getNamespace().equals(expectedNamespace), + "Unexpected namespace %s for element %s, should be %s", getNamespace(), expectedNamespace); + } + + public String getName() { + return element.getTagName(); + } + + public String getAttribute(String attributeName) { + return element.getAttribute(attributeName); + } + + public String getAttribute(String attributeName, String namespace) { + return element.getAttributeNS(namespace, attributeName); + } + + public void appendChild(Element element) { + this.element.appendChild(element); + // Element newElement = (Element) element.cloneNode(true); + // newElement.appendChild(configElement); + // return XmlElement.fromDomElement(newElement); + } + + public Element getDomElement() { + return element; + } + + public Map getAttributes() { + + Map mappedAttributes = Maps.newHashMap(); + + NamedNodeMap attributes = element.getAttributes(); + for (int i = 0; i < attributes.getLength(); i++) { + Attr attr = (Attr) attributes.item(i); + mappedAttributes.put(attr.getNodeName(), attr); + } + + return mappedAttributes; + } + + /** + * Non recursive + */ + private List getChildElementsInternal(ElementFilteringStrategy strat) { + NodeList childNodes = element.getChildNodes(); + final List result = new ArrayList<>(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node item = childNodes.item(i); + if (item instanceof Element == false) + continue; + if (strat.accept((Element) item)) + result.add(new XmlElement((Element) item)); + } + + return result; + } + + public List getChildElements() { + return getChildElementsInternal(new ElementFilteringStrategy() { + @Override + public boolean accept(Element e) { + return true; + } + }); + } + + public List getChildElementsWithinNamespace(final String childName, String namespace) { + return Lists.newArrayList(Collections2.filter(getChildElementsWithinNamespace(namespace), + new Predicate() { + @Override + public boolean apply(@Nullable XmlElement xmlElement) { + return xmlElement.getName().equals(childName); + } + })); + } + + public List getChildElementsWithinNamespace(final String namespace) { + return getChildElementsInternal(new ElementFilteringStrategy() { + @Override + public boolean accept(Element e) { + return XmlElement.fromDomElement(e).getNamespace().equals(namespace); + } + + }); + } + + public List getChildElements(final String tagName) { + return getChildElementsInternal(new ElementFilteringStrategy() { + @Override + public boolean accept(Element e) { + return e.getTagName().equals(tagName); + } + }); + } + + public XmlElement getOnlyChildElement(String childName) { + List nameElements = getChildElements(childName); + Preconditions.checkState(nameElements.size() == 1, "One element " + childName + " expected in " + toString()); + return nameElements.get(0); + } + + public Optional getOnlyChildElementOptionally(String childName) { + try { + return Optional.of(getOnlyChildElement(childName)); + } catch (Exception e) { + return Optional.absent(); + } + } + + public Optional getOnlyChildElementOptionally(String childName, String namespace) { + try { + return Optional.of(getOnlyChildElement(childName, namespace)); + } catch (Exception e) { + return Optional.absent(); + } + } + + public XmlElement getOnlyChildElementWithSameNamespace(String childName) { + return getOnlyChildElement(childName, getNamespace()); + } + + public Optional getOnlyChildElementWithSameNamespaceOptionally(String childName) { + try { + return Optional.of(getOnlyChildElement(childName, getNamespace())); + } catch (Exception e) { + return Optional.absent(); + } + } + + public XmlElement getOnlyChildElementWithSameNamespace() { + XmlElement childElement = getOnlyChildElement(); + childElement.checkNamespace(getNamespace()); + return childElement; + } + + public Optional getOnlyChildElementWithSameNamespaceOptionally() { + try { + XmlElement childElement = getOnlyChildElement(); + childElement.checkNamespace(getNamespace()); + return Optional.of(childElement); + } catch (Exception e) { + return Optional.absent(); + } + } + + public XmlElement getOnlyChildElement(final String childName, String namespace) { + List children = getChildElementsWithinNamespace(namespace); + children = Lists.newArrayList(Collections2.filter(children, new Predicate() { + @Override + public boolean apply(@Nullable XmlElement xmlElement) { + return xmlElement.getName().equals(childName); + } + })); + Preconditions.checkState(children.size() == 1, "One element %s:%s expected in %s but was %s", namespace, + childName, toString(), children.size()); + return children.get(0); + } + + public XmlElement getOnlyChildElement() { + List children = getChildElements(); + Preconditions.checkState(children.size() == 1, "One element expected in %s but was %s", toString(), + children.size()); + return children.get(0); + } + + public String getTextContent() { + Node textChild = element.getFirstChild(); + Preconditions.checkState(textChild instanceof Text, getName() + " should contain text"); + String content = textChild.getTextContent(); + // Trim needed + return content.trim(); + } + + public String getNamespaceAttribute() { + String attribute = element.getAttribute(XmlUtil.XMLNS_ATTRIBUTE_KEY); + Preconditions.checkState(attribute != null && !attribute.equals(""), "Element %s must specify a %s attribute", + toString(), XmlUtil.XMLNS_ATTRIBUTE_KEY); + return attribute; + } + + public String getNamespace() { + String namespaceURI = element.getNamespaceURI(); + Preconditions.checkState(namespaceURI != null, "No namespace defined for %s", this); + return namespaceURI.toString(); + } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("XmlElement{"); + sb.append("name='").append(getName()).append('\''); + if (element.getNamespaceURI() != null) { + sb.append(", namespace='").append(getNamespace()).append('\''); + } + sb.append('}'); + return sb.toString(); + } + + /** + * Search for element's attributes defining namespaces. Look for the one + * namespace that matches prefix of element's text content. E.g. + * + *

+     * <type
+     * xmlns:th-java="urn:opendaylight:params:xml:ns:yang:controller:threadpool:impl">th-java:threadfactory-naming</type>
+     * 
+ * + * returns {"th-java","urn:.."}. If no prefix is matched, then default + * namespace is returned with empty string as key. If no default namespace + * is found value will be null. + */ + public Map.Entry findNamespaceOfTextContent() { + Map namespaces = extractNamespaces(element); + String textContent = getTextContent(); + int indexOfColon = textContent.indexOf(":"); + String prefix; + if (indexOfColon > -1) { + prefix = textContent.substring(0, indexOfColon); + } else { + prefix = ""; + } + if (namespaces.containsKey(prefix) == false) { + throw new IllegalArgumentException("Cannot find namespace for " + element + ". Prefix from content is " + + prefix + ". Found namespaces " + namespaces); + } + return Maps.immutableEntry(prefix, namespaces.get(prefix)); + } + + public List getChildElementsWithSameNamespace(final String childName) { + List children = getChildElementsWithinNamespace(getNamespace()); + return Lists.newArrayList(Collections2.filter(children, new Predicate() { + @Override + public boolean apply(@Nullable XmlElement xmlElement) { + return xmlElement.getName().equals(childName); + } + })); + } + + public void checkUnrecognisedElements(List recognisedElements, + XmlElement... additionalRecognisedElements) { + List childElements = getChildElements(); + childElements.removeAll(recognisedElements); + for (XmlElement additionalRecognisedElement : additionalRecognisedElements) { + childElements.remove(additionalRecognisedElement); + } + Preconditions.checkState(childElements.isEmpty(), "Unrecognised elements %s in %s", childElements, this); + } + + public void checkUnrecognisedElements(XmlElement... additionalRecognisedElements) { + checkUnrecognisedElements(Collections. emptyList(), additionalRecognisedElements); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + XmlElement that = (XmlElement) o; + + if (!element.isEqualNode(that.element)) + return false; + + return true; + } + + @Override + public int hashCode() { + return element.hashCode(); + } + + private static interface ElementFilteringStrategy { + boolean accept(Element e); + } +} diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/XmlNetconfConstants.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/XmlNetconfConstants.java new file mode 100644 index 0000000000..0791812910 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/XmlNetconfConstants.java @@ -0,0 +1,41 @@ +/* + * 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.netconf.util.xml; + +public class XmlNetconfConstants { + + public static final String MOUNTPOINTS = "mountpoints"; + public static final String MOUNTPOINT = "mountpoint"; + public static final String ID = "id"; + public static final String CAPABILITY = "capability"; + public static final String CAPABILITIES = "capabilities"; + public static final String COMMIT = "commit"; + public static final String TYPE_KEY = "type"; + public static final String MODULE_KEY = "module"; + public static final String INSTANCE_KEY = "instance"; + public static final String OPERATION_ATTR_KEY = "operation"; + public static final String SERVICES_KEY = "services"; + public static final String CONFIG_KEY = "config"; + public static final String MODULES_KEY = "modules"; + public static final String CONFIGURATION_KEY = "configuration"; + public static final String DATA_KEY = "data"; + public static final String OK = "ok"; + public static final String FILTER = "filter"; + public static final String SOURCE_KEY = "source"; + public static final String RPC_KEY = "rpc"; + public static final String RPC_REPLY_KEY = "rpc-reply"; + public static final String RPC_ERROR = "rpc-error"; + public static final String NAME_KEY = "name"; + // + // + public static final String RFC4741_TARGET_NAMESPACE = "urn:ietf:params:xml:ns:netconf:base:1.0"; + public static final String URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0 = "urn:ietf:params:xml:ns:netconf:base:1.0"; + public static final String URN_IETF_PARAMS_XML_NS_YANG_IETF_NETCONF_MONITORING = "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring"; + // TODO where to store namespace of config ? + public static final String URN_OPENDAYLIGHT_PARAMS_XML_NS_YANG_CONTROLLER_CONFIG = "urn:opendaylight:params:xml:ns:yang:controller:config"; +} diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/XmlNetconfValidator.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/XmlNetconfValidator.java new file mode 100644 index 0000000000..de0ebccdca --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/XmlNetconfValidator.java @@ -0,0 +1,41 @@ +/* + * 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.netconf.util.xml; + +import java.io.IOException; +import java.io.InputStream; + +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMSource; +import javax.xml.validation.Schema; +import javax.xml.validation.Validator; + +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +import com.google.common.base.Preconditions; + +public class XmlNetconfValidator { + static final Schema schema; + + static { + final InputStream xmlSchema = XmlNetconfValidator.class.getResourceAsStream("/xml.xsd"); + Preconditions.checkNotNull(xmlSchema, "Cannot find xml.xsd"); + + final InputStream rfc4714Schema = XmlNetconfValidator.class.getResourceAsStream("/rfc4741.xsd"); + Preconditions.checkNotNull(rfc4714Schema, "Cannot find rfc4741.xsd"); + schema = XmlUtil.loadSchema(xmlSchema, rfc4714Schema); + } + + public static void validate(Document inputDocument) throws SAXException, IOException { + final Validator validator = schema.newValidator(); + final Source source = new DOMSource(inputDocument); + validator.validate(source); + } +} diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/XmlUtil.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/XmlUtil.java new file mode 100644 index 0000000000..a2d43b2279 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/XmlUtil.java @@ -0,0 +1,175 @@ +/* + * 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.netconf.util.xml; + +import java.io.*; + +import javax.xml.XMLConstants; +import javax.xml.namespace.QName; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.*; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathExpressionException; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.xml.sax.SAXException; + +import com.google.common.base.Charsets; + +public class XmlUtil { + + public static final String XMLNS_ATTRIBUTE_KEY = "xmlns"; + + public static Element readXmlToElement(String xmlContent) throws SAXException, IOException { + Document doc = readXmlToDocument(xmlContent); + return doc.getDocumentElement(); + } + + public static Element readXmlToElement(InputStream xmlContent) throws SAXException, IOException { + Document doc = readXmlToDocument(xmlContent); + return doc.getDocumentElement(); + } + + public static Document readXmlToDocument(String xmlContent) throws SAXException, IOException { + return readXmlToDocument(new ByteArrayInputStream(xmlContent.getBytes(Charsets.UTF_8))); + } + + public static Document readXmlToDocument(InputStream xmlContent) throws SAXException, IOException { + DocumentBuilderFactory factory = getDocumentBuilderFactory(); + DocumentBuilder dBuilder; + try { + dBuilder = factory.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + throw new RuntimeException(e); + } + Document doc = dBuilder.parse(xmlContent); + + doc.getDocumentElement().normalize(); + return doc; + } + + public static Element readXmlToElement(File xmlFile) throws SAXException, IOException { + return readXmlToDocument(new FileInputStream(xmlFile)).getDocumentElement(); + } + + private static final DocumentBuilderFactory getDocumentBuilderFactory() { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + factory.setCoalescing(true); + // factory.setValidating(true); + factory.setIgnoringElementContentWhitespace(true); + factory.setIgnoringComments(true); + return factory; + } + + public static Document newDocument() { + DocumentBuilderFactory factory = getDocumentBuilderFactory(); + try { + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.newDocument(); + return document; + } catch (ParserConfigurationException e) { + throw new RuntimeException(e); + } + } + + public static Element createTextElement(Document document, String name, String content) { + Element typeElement = document.createElement(name); + typeElement.appendChild(document.createTextNode(content)); + return typeElement; + } + + public static void addNamespaceAttr(Element root, String namespace) { + root.setAttribute(XMLNS_ATTRIBUTE_KEY, namespace); + } + + public static void addPrefixedNamespaceAttr(Element root, String prefix, String namespace) { + root.setAttribute(concat(XMLNS_ATTRIBUTE_KEY, prefix), namespace); + } + + public static Element createPrefixedTextElement(Document document, String key, String prefix, String moduleName) { + return createTextElement(document, key, concat(prefix, moduleName)); + } + + private static String concat(String prefix, String value) { + return prefix + ":" + value; + } + + public static String toString(Document document) { + return toString(document.getDocumentElement()); + } + + public static String toString(Element xml) { + return toString(xml, false); + } + + public static String toString(XmlElement xmlElement) { + return toString(xmlElement.getDomElement(), false); + } + + public static String toString(Element xml, boolean addXmlDeclaration) { + try { + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, addXmlDeclaration == true ? "no" : "yes"); + + StreamResult result = new StreamResult(new StringWriter()); + DOMSource source = new DOMSource(xml); + transformer.transform(source, result); + + String xmlString = result.getWriter().toString(); + return xmlString; + } catch (IllegalArgumentException | TransformerFactoryConfigurationError | TransformerException e) { + throw new RuntimeException("Unable to serialize xml element " + xml, e); + } + } + + public static String toString(Document doc, boolean addXmlDeclaration) { + return toString(doc.getDocumentElement(), addXmlDeclaration); + } + + public static Schema loadSchema(InputStream... fromStreams) { + Source[] sources = new Source[fromStreams.length]; + int i = 0; + for (InputStream stream : fromStreams) { + sources[i++] = new StreamSource(stream); + } + + final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + try { + return schemaFactory.newSchema(sources); + } catch (SAXException e) { + throw new IllegalStateException("Failed to instantiate XML schema", e); + } + } + + public static Object evaluateXPath(XPathExpression expr, Object rootNode, QName returnType) { + try { + return expr.evaluate(rootNode, returnType); + } catch (XPathExpressionException e) { + throw new IllegalStateException("Error while evaluating xpath expression " + expr, e); + } + } + + public static Document createDocumentCopy(Document original) { + final Document copiedDocument = newDocument(); + final Node copiedRoot = copiedDocument.importNode(original.getDocumentElement(), true); + copiedDocument.appendChild(copiedRoot); + return copiedDocument; + } +} diff --git a/opendaylight/netconf/netconf-util/src/main/resources/org/opendaylight/controller/netconf/util/messages/server_error.xml b/opendaylight/netconf/netconf-util/src/main/resources/org/opendaylight/controller/netconf/util/messages/server_error.xml new file mode 100644 index 0000000000..ff0c0c52aa --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/resources/org/opendaylight/controller/netconf/util/messages/server_error.xml @@ -0,0 +1,16 @@ + + + + *** transport/rpc/protocol/application + *** RFC 4741 Appendix A. NETCONF Error List + *** error/warning + + + + + diff --git a/opendaylight/netconf/netconf-util/src/main/resources/rfc4741.xsd b/opendaylight/netconf/netconf-util/src/main/resources/rfc4741.xsd new file mode 100644 index 0000000000..990049d34a --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/resources/rfc4741.xsd @@ -0,0 +1,585 @@ + + + + + + + + This import accesses the xml: attribute groups for the + xml:lang as declared on the error-message element. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Use of the rollback-on-error value requires + the :rollback-on-error capability. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Use of the xpath value requires the :xpath capability. + + + + + + + + + + + + + + + + + + + + + The startup datastore can be used only if the :startup + capability is advertised. The candidate datastore can + be used only if the :candidate datastore is advertised. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Use of the url element requires the :url capability. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Use of the test-option element requires the + :validate capability. Use of the url element + requires the :url capability. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The validate operation requires the :validate capability. + + + + + + + + + + + + + + + + + + + + + + + The commit operation requires the :candidate capability. + + + + + + + + Use of the confirmed and confirm-timeout elements + requires the :confirmed-commit capability. + + + + + + + + + + + + + + The discard-changes operation requires the + :candidate capability. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/opendaylight/netconf/netconf-util/src/main/resources/xml.xsd b/opendaylight/netconf/netconf-util/src/main/resources/xml.xsd new file mode 100644 index 0000000000..0f7db9d553 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/resources/xml.xsd @@ -0,0 +1,327 @@ + + + + + + +
+

About the XML namespace

+ +
+

+ This schema document describes the XML namespace, in a form + suitable for import by other schema documents. +

+

+ See + + http://www.w3.org/XML/1998/namespace.html + + and + + http://www.w3.org/TR/REC-xml + + for information + about this namespace. +

+

+ Note that local names in this namespace are intended to be + defined only by the World Wide Web Consortium or its subgroups. + The names currently defined in this namespace are listed below. + They should not be used with conflicting semantics by any Working + Group, specification, or document instance. +

+

+ See further below in this document for more information about + how to refer to this schema document from your own + XSD schema documents + + and aboutthe + namespace-versioning policy governing this schema document. +

+
+
+
+
+ + + + +
+ +

lang (as an attribute name)

+

+ denotes an attribute whose value + is a language code for the natural language of the content of + any element; its value is inherited. This name is reserved + by virtue of its definition in the XML specification. +

+ +
+
+

Notes

+

+ Attempting to install the relevant ISO 2- and 3-letter + codes as the enumerated possible values is probably never + going to be a realistic possibility. +

+

+ See BCP 47 at + + http://www.rfc-editor.org/rfc/bcp/bcp47.txt + + and the IANA language subtag registry at + + http://www.iana.org/assignments/language-subtag-registry + + for further information. +

+

+ The union allows for the 'un-declaration' of xml:lang with + the empty string. +

+
+
+
+ + + + + + + + + +
+ + + + +
+ +

space (as an attribute name)

+

+ denotes an attribute whose + value is a keyword indicating what whitespace processing + discipline is intended for the content of the element; its + value is inherited. This name is reserved by virtue of its + definition in the XML specification. +

+ +
+
+
+ + + + + + +
+ + + + +
+ +

base (as an attribute name)

+

+ denotes an attribute whose value + provides a URI to be used as the base for interpreting any + relative URIs in the scope of the element on which it + appears; its value is inherited. This name is reserved + by virtue of its definition in the XML Base specification. +

+ +

+ See + http://www.w3.org/TR/xmlbase/ + + for information about this attribute. +

+
+
+
+
+ + + + +
+ +

id (as an attribute name)

+

+ denotes an attribute whose value + should be interpreted as if declared to be of type ID. + This name is reserved by virtue of its definition in the + xml:id specification. +

+ +

+ See + http://www.w3.org/TR/xml-id/ + + for information about this attribute. +

+
+
+
+
+ + + + + + + + + + +
+ +

Father (in any context at all)

+ +
+

+ denotes Jon Bosak, the chair of + the original XML Working Group. This name is reserved by + the following decision of the W3C XML Plenary and + XML Coordination groups: +

+
+

+ In appreciation for his vision, leadership and + dedication the W3C XML Plenary on this 10th day of + February, 2000, reserves for Jon Bosak in perpetuity + the XML name "xml:Father". +

+
+
+
+
+
+ + + +
+

+ About this schema document +

+ +
+

+ This schema defines attributes and an attribute group suitable + for use by schemas wishing to allowxml:base, + xml:lang, + xml:space + or + xml:id + attributes on elements they define. +

+

+ To enable this, such a schema must import this schema for + the XML namespace, e.g. as follows: +

+
+                        <schema . . .>
+                        . . .
+                        <import namespace="http://www.w3.org/XML/1998/namespace"
+                        schemaLocation="http://www.w3.org/2001/xml.xsd"/>
+                    
+

+ or +

+
+                        <import namespace="http://www.w3.org/XML/1998/namespace"
+                        schemaLocation="http://www.w3.org/2009/01/xml.xsd"/>
+                    
+

+ Subsequently, qualified reference to any of the attributes or the + group defined below will have the desired effect, e.g. +

+
+                        <type . . .>
+                        . . .
+                        <attributeGroup ref="xml:specialAttrs"/>
+                    
+

+ will define a type which will schema-validate an instance element + with any of those attributes. +

+
+
+
+
+ + + +
+

+ Versioning policy for this schema document +

+
+

+ In keeping with the XML Schema WG's standard versioning + policy, this schema document will persist at + + http://www.w3.org/2009/01/xml.xsd. +

+

+ At the date of issue it can also be found at + + http://www.w3.org/2001/xml.xsd. +

+

+ The schema document at that URI may however change in the future, + in order to remain compatible with the latest version of XML + Schema itself, or with the XML namespace itself. In other words, + if the XML Schema or XML namespaces change, the version of this + document at + + http://www.w3.org/2001/xml.xsd + + will change accordingly; the version at + + http://www.w3.org/2009/01/xml.xsd + + will not change. +

+

+ Previous dated (and unchanging) versions of this schema + document are at: +

+ +
+
+
+
+ +
+ diff --git a/opendaylight/netconf/netconf-util/src/test/java/org/opendaylight/controller/netconf/util/test/XmlFileLoader.java b/opendaylight/netconf/netconf-util/src/test/java/org/opendaylight/controller/netconf/util/test/XmlFileLoader.java new file mode 100644 index 0000000000..8b60719ebe --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/java/org/opendaylight/controller/netconf/util/test/XmlFileLoader.java @@ -0,0 +1,72 @@ +/* + * 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.netconf.util.test; + +import com.google.common.base.Charsets; +import com.google.common.base.Preconditions; +import com.google.common.io.CharStreams; +import com.google.common.io.InputSupplier; +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +public class XmlFileLoader { + + public static NetconfMessage xmlFileToNetconfMessage(final String fileName) throws IOException, SAXException, + ParserConfigurationException { + return new NetconfMessage(xmlFileToDocument(fileName)); + } + + public static Element xmlFileToElement(final String fileName) throws IOException, SAXException, + ParserConfigurationException { + return xmlFileToDocument(fileName).getDocumentElement(); + } + + public static String xmlFileToString(final String fileName) throws IOException, SAXException, + ParserConfigurationException { + return XmlUtil.toString(xmlFileToDocument(fileName)); + } + + public static Document xmlFileToDocument(final String fileName) throws IOException, SAXException, + ParserConfigurationException { + try (InputStream resourceAsStream = XmlFileLoader.class.getClassLoader().getResourceAsStream(fileName)) { + Preconditions.checkNotNull(resourceAsStream); + final Document doc = XmlUtil.readXmlToDocument(resourceAsStream); + return doc; + } + } + + public static String fileToString(final String fileName) throws IOException { + try (InputStream resourceAsStream = XmlFileLoader.class.getClassLoader().getResourceAsStream(fileName)) { + Preconditions.checkNotNull(resourceAsStream); + + InputSupplier supplier = new InputSupplier() { + @Override + public InputStream getInput() throws IOException { + return resourceAsStream; + } + }; + + InputSupplier readerSupplier = CharStreams.newReaderSupplier(supplier, Charsets.UTF_8); + + return CharStreams.toString(readerSupplier); + } + } + + public static InputStream getResourceAsStream(final String fileName) { + return XmlFileLoader.class.getClassLoader().getResourceAsStream(fileName); + } +} diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/client_hello.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/client_hello.xml new file mode 100644 index 0000000000..4e34591284 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/client_hello.xml @@ -0,0 +1,5 @@ + + + urn:ietf:params:netconf:base:1.0 + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/client_hello_with_auth.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/client_hello_with_auth.xml new file mode 100644 index 0000000000..174640c4f9 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/client_hello_with_auth.xml @@ -0,0 +1,8 @@ +[tomas;10.0.0.0/10000;tcp;1000;1000;;/home/tomas;;] + + + + urn:ietf:params:netconf:base:1.0 + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/close-session.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/close-session.xml new file mode 100644 index 0000000000..32d6446285 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/close-session.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/closeSession.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/closeSession.xml new file mode 100644 index 0000000000..2b0c0d01ca --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/closeSession.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/commit.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/commit.xml new file mode 100644 index 0000000000..ffdf132153 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/commit.xml @@ -0,0 +1,3 @@ + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/communicationError/testClientSendsRpcReply_expectedResponse.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/communicationError/testClientSendsRpcReply_expectedResponse.xml new file mode 100644 index 0000000000..4d85d336e1 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/communicationError/testClientSendsRpcReply_expectedResponse.xml @@ -0,0 +1,11 @@ + + + + protocol + unknown-element + error + + rpc-reply + + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/communicationError/testClientSendsRpcReply_request.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/communicationError/testClientSendsRpcReply_request.xml new file mode 100644 index 0000000000..72e7780352 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/communicationError/testClientSendsRpcReply_request.xml @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/communicationError/testRpcWithoutMessageId_expectedResponse.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/communicationError/testRpcWithoutMessageId_expectedResponse.xml new file mode 100644 index 0000000000..c28b93dffb --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/communicationError/testRpcWithoutMessageId_expectedResponse.xml @@ -0,0 +1,12 @@ + + + + rpc + missing-attribute + error + + message-id + rpc + + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/communicationError/testRpcWithoutMessageId_request.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/communicationError/testRpcWithoutMessageId_request.xml new file mode 100644 index 0000000000..419d006827 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/communicationError/testRpcWithoutMessageId_request.xml @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/databaseInteraction/client_get_request_ConfigRegistry.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/databaseInteraction/client_get_request_ConfigRegistry.xml new file mode 100644 index 0000000000..dfed1a317a --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/databaseInteraction/client_get_request_ConfigRegistry.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/databaseInteraction/confg_subsystem_expected_reply.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/databaseInteraction/confg_subsystem_expected_reply.xml new file mode 100644 index 0000000000..ef8696dc52 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/databaseInteraction/confg_subsystem_expected_reply.xml @@ -0,0 +1,29 @@ + + + + + + testing-threadpool + modifiable-threadpool + + + fixed + 10 + false + + + + + testing-threadpool + modifiable-threadpool + + + fixed + 10 + false + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/discardChanges.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/discardChanges.xml new file mode 100644 index 0000000000..738a6657d3 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/discardChanges.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig.xml new file mode 100644 index 0000000000..904c0a6322 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig.xml @@ -0,0 +1,117 @@ + + + + + + + set + + merge + + + + + dep + + test-impl:impl-dep + + + + + dep2 + + test-impl:impl-dep + + + + + + test-impl:impl-netconf + + + test1 + + 44 + 8 + 1 + 0 + configAttributeType + + 444 + 4444 + 454 + + 44 + + 4 + + 4 + + 4 + + 44 + 545 + 454545 + false + + + + 456 + + 44 + + + 4 + 999 + 4 + + port1 + 456 + 44 + + + port23 + 456 + 44 + + + testing + ref_dep + + + + + + test-impl:impl-netconf + + test2 + + testing + ref_dep + + + + + + + testing + + ref_dep + /config/modules/module[name='impl-dep']/instance[name='dep'] + + + + ref_dep_2 + /config/modules/module[name='impl-dep']/instance[name='dep2'] + + + + ref_test1 + /config/modules/module[name='impl-netconf']/instance[name='test1'] + + + + + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig_expectedResult.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig_expectedResult.xml new file mode 100644 index 0000000000..7f884dc43c --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig_expectedResult.xml @@ -0,0 +1,19 @@ + + + + + + + + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig_merge_threadfactory.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig_merge_threadfactory.xml new file mode 100644 index 0000000000..ad7c84f3c9 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig_merge_threadfactory.xml @@ -0,0 +1,27 @@ + + + + + + + merge + set + + + + threadfactory-naming-instance + + th-java:threadfactory-naming + + + prefixDefinedInXML + + + + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig_merge_yang-test.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig_merge_yang-test.xml new file mode 100644 index 0000000000..5711a68944 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig_merge_yang-test.xml @@ -0,0 +1,23 @@ + + + + + + + merge + set + + + + impl-dep-instance + th-java:impl-dep + + + + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig_none.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig_none.xml new file mode 100644 index 0000000000..42021c59a3 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig_none.xml @@ -0,0 +1,109 @@ + + + + + + none + stop-on-error + + + + dep + + test-impl:impl-dep + + + + + dep2 + + test-impl:impl-dep + + + + + + test-impl:impl-netconf + + test1 + 44 + 8 + 7 + 9 + + 444 + 4444 + 454 + + 44 + + 4 + + 4 + + 4 + + 44 + 545 + 454545 + false + + + + 456 + + 44 + + + 4 + 999 + 4 + + port1 + 456 + 44 + + + port23 + 456 + 44 + + + testing + ref_dep + + + + + test-impl:impl-netconf + + test2 + + testing + ref_dep + + + + + + testing + + ref_dep + /config/modules/module[name='impl-dep']/instance[name='dep'] + + + + ref_dep_2 + /config/modules/module[name='impl-dep']/instance[name='dep2'] + + + + ref_test1 + /config/modules/module[name='impl-netconf']/instance[name='test1'] + + + + + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig_remove.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig_remove.xml new file mode 100644 index 0000000000..9d06d98f1c --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig_remove.xml @@ -0,0 +1,43 @@ + + + + + + none + + + + dep + + test-impl:impl-dep + + + + + dep2 + + test-impl:impl-dep + + + + + + test-impl:impl-netconf + + test1 + + + + + test-impl:impl-netconf + + test2 + + + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig_replace_default.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig_replace_default.xml new file mode 100644 index 0000000000..3c06b85b49 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig_replace_default.xml @@ -0,0 +1,27 @@ + + + + + + replace + + + + + dep + + test-impl:impl-dep + + + + + dep2 + + test-impl:impl-dep + + + + + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig_replace_default_ex.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig_replace_default_ex.xml new file mode 100644 index 0000000000..91183cb079 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig_replace_default_ex.xml @@ -0,0 +1,21 @@ + + + + + + replace + + + + impl-dep + dep + + + impl-dep + dep2 + + + + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig_replace_module.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig_replace_module.xml new file mode 100644 index 0000000000..ed3ff6abc6 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig_replace_module.xml @@ -0,0 +1,16 @@ + + + + + + + + + impl-dep + dep + + + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig_replace_module_ex.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig_replace_module_ex.xml new file mode 100644 index 0000000000..7c7679cfe2 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/editConfig_replace_module_ex.xml @@ -0,0 +1,16 @@ + + + + + + replace + + + + impl-dep + dep + + + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/edit_config.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/edit_config.xml new file mode 100644 index 0000000000..05866a6e73 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/edit_config.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/get.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/get.xml new file mode 100644 index 0000000000..7daadbda8b --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/get.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/getConfig.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/getConfig.xml new file mode 100644 index 0000000000..39efb4961b --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/getConfig.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/getConfig_candidate.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/getConfig_candidate.xml new file mode 100644 index 0000000000..5e65bfd2ed --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/getConfig_candidate.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/get_schema-no-version.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/get_schema-no-version.xml new file mode 100644 index 0000000000..fa59e59f8b --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/get_schema-no-version.xml @@ -0,0 +1,6 @@ + + + threadpool-api + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/get_schema.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/get_schema.xml new file mode 100644 index 0000000000..79f046ea5e --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/get_schema.xml @@ -0,0 +1,10 @@ + + + threadpool-api + 2010-09-24 + ncm:yang + + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/handshake/client_hello_with_session_id.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/handshake/client_hello_with_session_id.xml new file mode 100644 index 0000000000..e950e44899 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/handshake/client_hello_with_session_id.xml @@ -0,0 +1,6 @@ + + + urn:ietf:params:netconf:base:1.0 + + 666 + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/handshake/client_hello_with_wrong_namespace.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/handshake/client_hello_with_wrong_namespace.xml new file mode 100644 index 0000000000..3f32f608f5 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/handshake/client_hello_with_wrong_namespace.xml @@ -0,0 +1,5 @@ + + + urn:ietf:params:netconf:base:1.0 + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/mount/editConfig_merge_threadfactory.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/mount/editConfig_merge_threadfactory.xml new file mode 100644 index 0000000000..e1e0237a1e --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/mount/editConfig_merge_threadfactory.xml @@ -0,0 +1,68 @@ + + + + + + + merge + + + + threadfactory-naming-instance + + th-java:threadfactory-naming + + + prefixDefinedInXML + + + + + + + localhost:12002 + + + + threadfactory-naming-instance + + th-java:threadfactory-naming + + + prefixDefinedInXML + + + + + + + localhost:12003 + + + + threadfactory-naming-instance + + th-java:threadfactory-naming + + + prefixDefinedInXML + + + + + + + + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/mount/mount12002.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/mount/mount12002.xml new file mode 100644 index 0000000000..f524de3cf3 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/mount/mount12002.xml @@ -0,0 +1,7 @@ + + + + localhost:12002 + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/mount/mount12003.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/mount/mount12003.xml new file mode 100644 index 0000000000..64ee057006 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/mount/mount12003.xml @@ -0,0 +1,7 @@ + + + + localhost:12003 + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/mount/unmount12002.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/mount/unmount12002.xml new file mode 100644 index 0000000000..c82db72b57 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/mount/unmount12002.xml @@ -0,0 +1,7 @@ + + + + localhost:12002 + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/namespaces/editConfig_differentNamespaceTO.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/namespaces/editConfig_differentNamespaceTO.xml new file mode 100644 index 0000000000..efa4690f6f --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/namespaces/editConfig_differentNamespaceTO.xml @@ -0,0 +1,117 @@ + + + + + + + set + + merge + + + + + dep + + test-impl:impl-dep + + + + + dep2 + + test-impl:impl-dep + + + + + + test-impl:impl-netconf + + + test1 + + 44 + 8 + 1 + 0 + configAttributeType + + 444 + 4444 + 454 + + 44 + + 4 + + 4 + + 4 + + 44 + 545 + 454545 + false + + + + 456 + + 44 + + + 4 + 999 + 4 + + port1 + 456 + 44 + + + port23 + 456 + 44 + + + testing + ref_dep + + + + + + test-impl:impl-netconf + + test2 + + testing + ref_dep + + + + + + + testing + + ref_dep + /config/modules/module[name='impl-dep']/instance[name='dep'] + + + + ref_dep_2 + /config/modules/module[name='impl-dep']/instance[name='dep2'] + + + + ref_test1 + /config/modules/module[name='impl-netconf']/instance[name='test1'] + + + + + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/namespaces/editConfig_sameAttrDifferentNamespaces.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/namespaces/editConfig_sameAttrDifferentNamespaces.xml new file mode 100644 index 0000000000..3dbb297c29 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/namespaces/editConfig_sameAttrDifferentNamespaces.xml @@ -0,0 +1,118 @@ + + + + + + + set + + merge + + + + + dep + + test-impl:impl-dep + + + + + dep2 + + test-impl:impl-dep + + + + + + test-impl:impl-netconf + + + test1 + + 44 + 44 + 8 + 1 + 0 + configAttributeType + + 444 + 4444 + 454 + + 44 + + 4 + + 4 + + 4 + + 44 + 545 + 454545 + false + + + + 456 + + 44 + + + 4 + 999 + 4 + + port1 + 456 + 44 + + + port23 + 456 + 44 + + + testing + ref_dep + + + + + + test-impl:impl-netconf + + test2 + + testing + ref_dep + + + + + + + testing + + ref_dep + /config/modules/module[name='impl-dep']/instance[name='dep'] + + + + ref_dep_2 + /config/modules/module[name='impl-dep']/instance[name='dep2'] + + + + ref_test1 + /config/modules/module[name='impl-netconf']/instance[name='test1'] + + + + + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/namespaces/editConfig_sameAttrDifferentNamespacesList.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/namespaces/editConfig_sameAttrDifferentNamespacesList.xml new file mode 100644 index 0000000000..504ccff78f --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/namespaces/editConfig_sameAttrDifferentNamespacesList.xml @@ -0,0 +1,117 @@ + + + + + + + set + + merge + + + + + dep + + test-impl:impl-dep + + + + + dep2 + + test-impl:impl-dep + + + + + + test-impl:impl-netconf + + + test1 + + 44 + 8 + 1 + 0 + configAttributeType + + 444 + 4444 + 454 + + 44 + + 4 + + 4 + + 4 + + 44 + 545 + 454545 + false + + + + 456 + + 44 + + + 4 + 999 + 4 + + port1 + 456 + 44 + + + port23 + 456 + 44 + + + testing + ref_dep + + + + + + test-impl:impl-netconf + + test2 + + testing + ref_dep + + + + + + + testing + + ref_dep + /config/modules/module[name='impl-dep']/instance[name='dep'] + + + + ref_dep_2 + /config/modules/module[name='impl-dep']/instance[name='dep2'] + + + + ref_test1 + /config/modules/module[name='impl-netconf']/instance[name='test1'] + + + + + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/namespaces/editConfig_typeNameConfigAttributeMatching.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/namespaces/editConfig_typeNameConfigAttributeMatching.xml new file mode 100644 index 0000000000..8398fdb588 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/namespaces/editConfig_typeNameConfigAttributeMatching.xml @@ -0,0 +1,116 @@ + + + + + + + set + + merge + + + + + dep + + test-impl:impl-dep + + + + + dep2 + + test-impl:impl-dep + + + + + + test-impl:impl-netconf + + + test1 + + 44 + 8 + 1 + 0 + + 444 + 4444 + 454 + + 44 + + 4 + + 4 + + 4 + + 44 + 545 + 454545 + false + + + + 456 + + 44 + + + 4 + 999 + 4 + + port1 + 456 + 44 + + + port23 + 456 + 44 + + + testing + ref_dep + + + + + + test-impl:impl-netconf + + test2 + + testing + ref_dep + + + + + + + testing + + ref_dep + /config/modules/module[name='impl-dep']/instance[name='dep'] + + + + ref_dep_2 + /config/modules/module[name='impl-dep']/instance[name='dep2'] + + + + ref_test1 + /config/modules/module[name='impl-netconf']/instance[name='test1'] + + + + + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/rpc.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/rpc.xml new file mode 100644 index 0000000000..b2a2ee30c8 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/rpc.xml @@ -0,0 +1,8 @@ + + + /data/modules/module[name='impl-netconf']/instance[name='instance'] + + testarg1 + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/rpcInner.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/rpcInner.xml new file mode 100644 index 0000000000..8bc504a56e --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/rpcInner.xml @@ -0,0 +1,7 @@ + + + + /data/modules/module[name='impl-netconf']/instance[name='instance2']/inner-running-data-additional[key='randomString_1003'] + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/rpcInnerInner.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/rpcInnerInner.xml new file mode 100644 index 0000000000..2356398e64 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/rpcInnerInner.xml @@ -0,0 +1,16 @@ + + + + /data/modules/module[name='impl-netconf']/instance[name='instance2']/inner-running-data[key='1015']/inner-inner-running-data[key='1017'] + + + + 456 + + + true + + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/threadpool-edit-config.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/threadpool-edit-config.xml new file mode 100644 index 0000000000..40f86e3155 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/threadpool-edit-config.xml @@ -0,0 +1,23 @@ + + + + + + + + + threadfactory-naming-instance + + th-java:threadfactory-naming + + + prefixDefinedInXML + + + + + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/unrecognised/editConfig_unrecognised1.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/unrecognised/editConfig_unrecognised1.xml new file mode 100644 index 0000000000..6b267c57a3 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/unrecognised/editConfig_unrecognised1.xml @@ -0,0 +1,36 @@ + + + + + + + set + + merge + + + + + dep + + test-impl:impl-dep + + error + + + + + + + + testing + + ref_dep + /config/modules/module[name='impl-dep']/instance[name='dep'] + + + + + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/unrecognised/editConfig_unrecognised2.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/unrecognised/editConfig_unrecognised2.xml new file mode 100644 index 0000000000..8ca7ee724a --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/unrecognised/editConfig_unrecognised2.xml @@ -0,0 +1,37 @@ + + + + + + + set + + merge + + + + + dep + + test-impl:impl-dep + + + + + + + error + + + + testing + + ref_dep + /config/modules/module[name='impl-dep']/instance[name='dep'] + + + + + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/unrecognised/editConfig_unrecognised3.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/unrecognised/editConfig_unrecognised3.xml new file mode 100644 index 0000000000..d3705cc3c4 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/unrecognised/editConfig_unrecognised3.xml @@ -0,0 +1,38 @@ + + + + + + + set + + merge + + + + + dep + + test-impl:impl-dep + + + + + + + + + error + l + + testing + + ref_dep + /config/modules/module[name='impl-dep']/instance[name='dep'] + + + + + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/unrecognised/editConfig_unrecognised4.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/unrecognised/editConfig_unrecognised4.xml new file mode 100644 index 0000000000..a485574969 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/unrecognised/editConfig_unrecognised4.xml @@ -0,0 +1,39 @@ + + + + + + + set + + merge + + + + + dep + + test-impl:impl-dep + + + + + + + + + l + + testing + error + + + ref_dep + /config/modules/module[name='impl-dep']/instance[name='dep'] + + + + + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/unrecognised/editConfig_unrecognised5.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/unrecognised/editConfig_unrecognised5.xml new file mode 100644 index 0000000000..654a183359 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/unrecognised/editConfig_unrecognised5.xml @@ -0,0 +1,39 @@ + + + + + + + set + + merge + + + + + dep + + test-impl:impl-dep + + + + + + + + + l + + testing + + + ref_dep + error + /config/modules/module[name='impl-dep']/instance[name='dep'] + + + + + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/unrecognised/editConfig_unrecognised6.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/unrecognised/editConfig_unrecognised6.xml new file mode 100644 index 0000000000..fc43a77406 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/unrecognised/editConfig_unrecognised6.xml @@ -0,0 +1,36 @@ + + + + + + + set + + merge + + + + error + + dep + + test-impl:impl-dep + + + + + + + + + testing + + ref_dep + /config/modules/module[name='impl-dep']/instance[name='dep'] + + + + + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/unrecognised/editConfig_unrecognised7.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/unrecognised/editConfig_unrecognised7.xml new file mode 100644 index 0000000000..1bea536934 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/unrecognised/editConfig_unrecognised7.xml @@ -0,0 +1,117 @@ + + + + + + + set + + merge + + + + + dep + + test-impl:impl-dep + + + + + dep2 + + test-impl:impl-dep + + + + + + test-impl:impl-netconf + + + test1 + + 44 + 8 + 1 + 0 + + error + 444 + 4444 + 454 + + 44 + + 4 + + 4 + + 4 + + 44 + 545 + 454545 + false + + + + 456 + + 44 + + + 4 + 999 + 4 + + port1 + 456 + 44 + + + port23 + 456 + 44 + + + testing + ref_dep + + + + + + test-impl:impl-netconf + + test2 + + testing + ref_dep + + + + + + + testing + + ref_dep + /config/modules/module[name='impl-dep']/instance[name='dep'] + + + + ref_dep_2 + /config/modules/module[name='impl-dep']/instance[name='dep2'] + + + + ref_test1 + /config/modules/module[name='impl-netconf']/instance[name='test1'] + + + + + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/unrecognised/editConfig_unrecognised8.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/unrecognised/editConfig_unrecognised8.xml new file mode 100644 index 0000000000..d0afb276aa --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/unrecognised/editConfig_unrecognised8.xml @@ -0,0 +1,117 @@ + + + + + + + set + + merge + + + + + dep + + test-impl:impl-dep + + + + + dep2 + + test-impl:impl-dep + + + + + + test-impl:impl-netconf + + + test1 + + 44 + 8 + 1 + 0 + + 444 + 4444 + 454 + + 44 + + 4 + + 4 + + 4 + + 44 + 545 + 454545 + false + + + + 456 + + 44 + + + 4 + 999 + 4 + + port1 + 456 + 44 + + + port23 + 456 + 44 + error + + + testing + ref_dep + + + + + + test-impl:impl-netconf + + test2 + + testing + ref_dep + + + + + + + testing + + ref_dep + /config/modules/module[name='impl-dep']/instance[name='dep'] + + + + ref_dep_2 + /config/modules/module[name='impl-dep']/instance[name='dep2'] + + + + ref_test1 + /config/modules/module[name='impl-netconf']/instance[name='test1'] + + + + + + + diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/validate.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/validate.xml new file mode 100644 index 0000000000..dab34b4757 --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/validate.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/opendaylight/netconf/pom.xml b/opendaylight/netconf/pom.xml new file mode 100644 index 0000000000..cff6dfa538 --- /dev/null +++ b/opendaylight/netconf/pom.xml @@ -0,0 +1,148 @@ + + 4.0.0 + + + org.opendaylight.controller + commons.opendaylight + 1.4.1-SNAPSHOT + ../commons/opendaylight + + + org.opendaylight.controller + 0.2.1-SNAPSHOT + netconf-subsystem + pom + ${project.artifactId} + + 3.0.4 + + + + + netconf-api + netconf-impl + config-netconf-connector + netconf-util + netconf-it + config-persister-impl + netconf-mapping-api + netconf-client + + + + 5.0.0 + 2.3.7 + 1.7.2 + 1.7 + 1.7 + + + + + junit + junit + test + + + ch.qos.logback + logback-classic + test + + + + + + + org.osgi + org.osgi.core + ${osgi.version} + + + org.opendaylight.bgpcep + mockito-configuration + ${bgpcep.version} + test + + + ${project.groupId} + config-api + ${config.version} + + + ${project.groupId} + config-manager + ${config.version} + + + ${project.groupId} + yang-jmx-generator + ${config.version} + + + ${project.groupId} + config-util + ${config.version} + + + ${project.groupId} + yang-store-api + ${config.version} + + + ${project.groupId} + yang-store-impl + ${config.version} + + + ${project.groupId} + yang-test + ${config.version} + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.5.1 + + ${java.version.source} + ${java.version.target} + ${java.version.source} + ${java.version.target} + + + + + + + org.apache.felix + maven-bundle-plugin + ${maven.bundle.version} + true + + + ${project.groupId}.${project.artifactId} + + + + + + + + + diff --git a/pom.xml b/pom.xml index 4abcd81c36..692c0c6b21 100644 --- a/pom.xml +++ b/pom.xml @@ -81,6 +81,7 @@ opendaylight/md-sal opendaylight/config + opendaylight/netconf