From: Ed Warnicke Date: Fri, 29 Nov 2013 11:16:05 +0000 (+0000) Subject: Merge "Choice and case resolving in JSON output" X-Git-Tag: jenkins-controller-bulk-release-prepare-only-2-1~305 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=49bfc449558d0306f0b6550bc5bdf41e5cafca44;hp=d5096acfd89788c89f26b393abe81bfd65292086 Merge "Choice and case resolving in JSON output" --- diff --git a/.gitignore b/.gitignore index bb3e26faf5..18817e7c35 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ opendaylight/northbound/integrationtest/logs/* *.iws .idea xtend-gen +classes diff --git a/opendaylight/commons/opendaylight/pom.xml b/opendaylight/commons/opendaylight/pom.xml index 794986a7bb..a652845770 100644 --- a/opendaylight/commons/opendaylight/pom.xml +++ b/opendaylight/commons/opendaylight/pom.xml @@ -1126,7 +1126,9 @@ - + + false + diff --git a/opendaylight/config/config-persister-api/src/main/java/org/opendaylight/controller/config/persist/api/ConfigSnapshotHolder.java b/opendaylight/config/config-persister-api/src/main/java/org/opendaylight/controller/config/persist/api/ConfigSnapshotHolder.java new file mode 100644 index 0000000000..654326a898 --- /dev/null +++ b/opendaylight/config/config-persister-api/src/main/java/org/opendaylight/controller/config/persist/api/ConfigSnapshotHolder.java @@ -0,0 +1,17 @@ +package org.opendaylight.controller.config.persist.api; + +import java.util.SortedSet; + +public interface ConfigSnapshotHolder { + + /** + * Get part of get-config document that contains just + */ + String getConfigSnapshot(); + + + /** + * Get only required capabilities referenced by the snapshot. + */ + SortedSet getCapabilities(); + } 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 index f9c9301b88..1448e553e3 100644 --- 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 @@ -10,23 +10,18 @@ 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 { +public interface Persister extends AutoCloseable { void persistConfig(ConfigSnapshotHolder configSnapshotHolder) throws IOException; Optional loadLastConfig() throws IOException; - public static interface ConfigSnapshotHolder { + @Override + void close(); - String getConfigSnapshot(); - - Set getCapabilities(); - } } diff --git a/opendaylight/config/config-persister-api/src/main/java/org/opendaylight/controller/config/persist/api/PropertiesProvider.java b/opendaylight/config/config-persister-api/src/main/java/org/opendaylight/controller/config/persist/api/PropertiesProvider.java new file mode 100644 index 0000000000..1d4139f885 --- /dev/null +++ b/opendaylight/config/config-persister-api/src/main/java/org/opendaylight/controller/config/persist/api/PropertiesProvider.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.config.persist.api; + +public interface PropertiesProvider { + /** + * Get property value for given key. Implementation of this interface is allowed to prefix + * the key with a namespace. + */ + String getProperty(String key); + + /** + * @return prefix + key as used in getProperty method. + */ + String getFullKeyForReporting(String key); +} 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/StorageAdapter.java similarity index 61% rename from opendaylight/config/config-persister-api/src/main/java/org/opendaylight/controller/config/persist/api/storage/StorageAdapter.java rename to opendaylight/config/config-persister-api/src/main/java/org/opendaylight/controller/config/persist/api/StorageAdapter.java index 9daf4a1325..524e7b5633 100644 --- 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/StorageAdapter.java @@ -6,17 +6,15 @@ * 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.osgi.framework.BundleContext; +package org.opendaylight.controller.config.persist.api; /** * Plugins for {@link org.opendaylight.controller.config.persist.api.Persister} * must implement this interface. */ -public interface StorageAdapter extends Persister { +public interface StorageAdapter { + + Persister instantiate(PropertiesProvider propertiesProvider); - void setProperties(BundleContext bundleContext); } diff --git a/opendaylight/config/config-persister-directory-adapter/pom.xml b/opendaylight/config/config-persister-directory-adapter/pom.xml new file mode 100644 index 0000000000..b2da71eea2 --- /dev/null +++ b/opendaylight/config/config-persister-directory-adapter/pom.xml @@ -0,0 +1,90 @@ + + + 4.0.0 + + config-subsystem + org.opendaylight.controller + 0.2.3-SNAPSHOT + .. + + config-persister-directory-adapter + ${project.artifactId} + bundle + + + + + ${project.groupId} + config-persister-api + + + org.apache.commons + commons-lang3 + + + com.google.guava + guava + + + org.slf4j + slf4j-api + + + commons-io + commons-io + + + + + org.opendaylight.bgpcep + mockito-configuration + 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 + + + com.google.common.base, + com.google.common.io, + org.apache.commons.io, + org.opendaylight.controller.config.persist.api, + org.slf4j + + + + + + + + diff --git a/opendaylight/config/config-persister-directory-adapter/src/main/java/org/opendaylight/controller/config/persist/storage/directory/DirectoryPersister.java b/opendaylight/config/config-persister-directory-adapter/src/main/java/org/opendaylight/controller/config/persist/storage/directory/DirectoryPersister.java new file mode 100644 index 0000000000..cf4ef98bdc --- /dev/null +++ b/opendaylight/config/config-persister-directory-adapter/src/main/java/org/opendaylight/controller/config/persist/storage/directory/DirectoryPersister.java @@ -0,0 +1,177 @@ +/* + * 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.directory; + +import com.google.common.base.Charsets; +import com.google.common.base.Optional; +import com.google.common.io.Files; +import org.apache.commons.io.IOUtils; +import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder; +import org.opendaylight.controller.config.persist.api.Persister; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; + +public class DirectoryPersister implements Persister { + private static final Logger logger = LoggerFactory.getLogger(DirectoryPersister.class); + private static final Charset ENCODING = Charsets.UTF_8; + + static final String MODULES_START = "//MODULES START"; + static final String SERVICES_START = "//SERVICES START"; + static final String CAPABILITIES_START = "//CAPABILITIES START"; + + + private final File storage; + private final String header, middle, footer; + + public DirectoryPersister(File storage) { + checkArgument(storage.exists() && storage.isDirectory(), "Storage directory does not exist: " + storage); + this.storage = storage; + header = readResource("header.txt"); + middle = readResource("middle.txt"); + footer = readResource("footer.txt"); + + } + + private static String readResource(String resource) { + try { + return IOUtils.toString(DirectoryPersister.class.getResourceAsStream("/" + resource)); + } catch (IOException e) { + throw new IllegalStateException("Cannot load " + resource, e); + } + } + + @Override + public void persistConfig(ConfigSnapshotHolder holder) throws IOException { + throw new UnsupportedOperationException("This adapter is read only. Please set readonly=true on " + getClass()); + } + + @Override + public Optional loadLastConfig() throws IOException { + File[] filesArray = storage.listFiles(); + if (filesArray.length == 0) { + return Optional.absent(); + } + List sortedFiles = new ArrayList<>(Arrays.asList(filesArray)); + Collections.sort(sortedFiles); + // combine all found files + + SortedSet combinedCapabilities = new TreeSet<>(); + StringBuilder modulesBuilder = new StringBuilder(), servicesBuilder = new StringBuilder(); + for (File file : sortedFiles) { + logger.trace("Adding file '{}' to combined result", file); + + final MyLineProcessor lineProcessor = new MyLineProcessor(); + Files.readLines(file, ENCODING, lineProcessor); + + modulesBuilder.append(lineProcessor.getModules()); + servicesBuilder.append(lineProcessor.getServices()); + combinedCapabilities.addAll(lineProcessor.getCapabilities()); + } + String combinedSnapshot = header + modulesBuilder.toString() + middle + servicesBuilder.toString() + footer; + ConfigSnapshotHolder result = new ConfigSnapshotHolderImpl(combinedSnapshot, combinedCapabilities); + return Optional.of(result); + } + + + @Override + public void close() { + + } + + @Override + public String toString() { + return "FileStorageAdapter [storage=" + storage + "]"; + } +} + +class MyLineProcessor implements com.google.common.io.LineProcessor { + + private boolean inModules, inServices, inCapabilities; + private final StringBuffer modulesBuffer = new StringBuffer(), servicesBuilder = new StringBuffer(); + private final SortedSet caps = new TreeSet<>(); + + @Override + public String getResult() { + return null; + } + + @Override + public boolean processLine(String line) throws IOException { + + String lineWithNewLine = line + System.lineSeparator(); + if (line.equals(DirectoryPersister.MODULES_START)) { + checkState(inModules == false && inServices == false && inCapabilities == false); + inModules = true; + } else if (line.equals(DirectoryPersister.SERVICES_START)) { + checkState(inModules == true && inServices == false && inCapabilities == false); + inModules = false; + inServices = true; + } else if (line.equals(DirectoryPersister.CAPABILITIES_START)) { + checkState(inModules == false && inServices == true && inCapabilities == false); + inServices = false; + inCapabilities = true; + } else if (inModules) { + modulesBuffer.append(lineWithNewLine); + } else if (inServices) { + servicesBuilder.append(lineWithNewLine); + } else { + caps.add(line); + } + return true; + } + + String getModules() { + checkState(inCapabilities); + return modulesBuffer.toString(); + } + + String getServices() { + checkState(inCapabilities); + return servicesBuilder.toString(); + } + + SortedSet getCapabilities() { + checkState(inCapabilities); + return caps; + } + +} + +class ConfigSnapshotHolderImpl implements ConfigSnapshotHolder { + + private final String snapshot; + private final SortedSet caps; + + public ConfigSnapshotHolderImpl(String configSnapshot, SortedSet capabilities) { + this.snapshot = configSnapshot; + this.caps = capabilities; + } + + @Override + public String getConfigSnapshot() { + return snapshot; + } + + @Override + public SortedSet getCapabilities() { + return caps; + } +} diff --git a/opendaylight/config/config-persister-directory-adapter/src/main/java/org/opendaylight/controller/config/persist/storage/directory/DirectoryStorageAdapter.java b/opendaylight/config/config-persister-directory-adapter/src/main/java/org/opendaylight/controller/config/persist/storage/directory/DirectoryStorageAdapter.java new file mode 100644 index 0000000000..69c8fba9da --- /dev/null +++ b/opendaylight/config/config-persister-directory-adapter/src/main/java/org/opendaylight/controller/config/persist/storage/directory/DirectoryStorageAdapter.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.config.persist.storage.directory; + +import com.google.common.base.Preconditions; +import org.opendaylight.controller.config.persist.api.Persister; +import org.opendaylight.controller.config.persist.api.PropertiesProvider; +import org.opendaylight.controller.config.persist.api.StorageAdapter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; + +/** + * StorageAdapter that retrieves initial configuration from a directory. If multiple files are present, snapshot and + * required capabilities will be merged together. Writing to this persister is not supported. + */ +public class DirectoryStorageAdapter implements StorageAdapter { + private static final Logger logger = LoggerFactory.getLogger(DirectoryStorageAdapter.class); + + public static final String DIRECTORY_STORAGE_PROP = "directoryStorage"; + + + @Override + public Persister instantiate(PropertiesProvider propertiesProvider) { + String fileStorageProperty = propertiesProvider.getProperty(DIRECTORY_STORAGE_PROP); + Preconditions.checkNotNull(fileStorageProperty, "Unable to find " + propertiesProvider.getFullKeyForReporting(DIRECTORY_STORAGE_PROP)); + File storage = new File(fileStorageProperty); + logger.debug("Using {}", storage); + return new DirectoryPersister(storage); + } + +} diff --git a/opendaylight/config/config-persister-directory-adapter/src/main/resources/footer.txt b/opendaylight/config/config-persister-directory-adapter/src/main/resources/footer.txt new file mode 100644 index 0000000000..d4dcc62822 --- /dev/null +++ b/opendaylight/config/config-persister-directory-adapter/src/main/resources/footer.txt @@ -0,0 +1,2 @@ + + diff --git a/opendaylight/config/config-persister-directory-adapter/src/main/resources/header.txt b/opendaylight/config/config-persister-directory-adapter/src/main/resources/header.txt new file mode 100644 index 0000000000..90ed41c3da --- /dev/null +++ b/opendaylight/config/config-persister-directory-adapter/src/main/resources/header.txt @@ -0,0 +1,2 @@ + + diff --git a/opendaylight/config/config-persister-directory-adapter/src/main/resources/middle.txt b/opendaylight/config/config-persister-directory-adapter/src/main/resources/middle.txt new file mode 100644 index 0000000000..f728cfdd95 --- /dev/null +++ b/opendaylight/config/config-persister-directory-adapter/src/main/resources/middle.txt @@ -0,0 +1,2 @@ + + diff --git a/opendaylight/config/config-persister-directory-adapter/src/test/java/org/opendaylight/controller/config/persist/storage/directory/DirectoryStorageAdapterTest.java b/opendaylight/config/config-persister-directory-adapter/src/test/java/org/opendaylight/controller/config/persist/storage/directory/DirectoryStorageAdapterTest.java new file mode 100644 index 0000000000..53ab4c210e --- /dev/null +++ b/opendaylight/config/config-persister-directory-adapter/src/test/java/org/opendaylight/controller/config/persist/storage/directory/DirectoryStorageAdapterTest.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.config.persist.storage.directory; + +import com.google.common.base.Optional; +import org.apache.commons.io.IOUtils; +import org.junit.Before; +import org.junit.Test; +import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder; + +import java.io.File; +import java.io.IOException; +import java.util.SortedSet; +import java.util.TreeSet; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class DirectoryStorageAdapterTest { + DirectoryPersister tested; + SortedSet expectedCapabilities; + String expectedSnapshot; + + @Before + public void setUp() throws Exception { + expectedCapabilities = new TreeSet<>(IOUtils.readLines(getClass().getResourceAsStream("/expectedCapabilities.txt"))); + expectedSnapshot = IOUtils.toString(getClass().getResourceAsStream("/expectedSnapshot.xml")); + } + + @Test + public void testEmptyDirectory() throws Exception { + File folder = new File("target/emptyFolder"); + folder.mkdir(); + tested = new DirectoryPersister((folder)); + assertEquals(Optional.absent(), tested.loadLastConfig()); + + try { + tested.persistConfig(new ConfigSnapshotHolder() { + @Override + public String getConfigSnapshot() { + throw new RuntimeException(); + } + + @Override + public SortedSet getCapabilities() { + throw new RuntimeException(); + } + }); + fail(); + } catch (UnsupportedOperationException e) { + + } + } + + private File getFolder(String folderName) { + File result = new File(("src/test/resources/" + + folderName).replace("/", File.separator)); + assertTrue(result + " is not a directory", result.isDirectory()); + return result; + } + + @Test + public void testOneFile() throws Exception { + File folder = getFolder("oneFile"); + tested = new DirectoryPersister((folder)); + assertExpected(); + } + + private void assertExpected() throws IOException { + Optional maybeResult = tested.loadLastConfig(); + assertTrue(maybeResult.isPresent()); + ConfigSnapshotHolder result = maybeResult.get(); + assertEquals(expectedCapabilities, result.getCapabilities()); + assertEquals(expectedSnapshot, result.getConfigSnapshot()); + } + + @Test + public void testTwoFiles() throws Exception { + File folder = getFolder("twoFiles"); + tested = new DirectoryPersister((folder)); + assertExpected(); + } + +} diff --git a/opendaylight/config/config-persister-directory-adapter/src/test/resources/expectedCapabilities.txt b/opendaylight/config/config-persister-directory-adapter/src/test/resources/expectedCapabilities.txt new file mode 100644 index 0000000000..84c85b740c --- /dev/null +++ b/opendaylight/config/config-persister-directory-adapter/src/test/resources/expectedCapabilities.txt @@ -0,0 +1,20 @@ +urn:opendaylight:l2:types?module=opendaylight-l2-types&revision=2013-08-27 +urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding?module=opendaylight-md-sal-binding&revision=2013-10-28 +urn:opendaylight:params:xml:ns:yang:controller:threadpool?module=threadpool&revision=2013-04-09 +urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom?module=opendaylight-md-sal-dom&revision=2013-10-28 +urn:opendaylight:params:xml:ns:yang:controller:config?module=config&revision=2013-04-05 +urn:ietf:params:netconf:capability:candidate:1.0 +urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring?module=ietf-netconf-monitoring&revision=2010-10-04 +urn:opendaylight:params:xml:ns:yang:controller:netty:eventexecutor?module=netty-event-executor&revision=2013-11-12 +urn:ietf:params:xml:ns:yang:rpc-context?module=rpc-context&revision=2013-06-17 +urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding:impl?module=opendaylight-sal-binding-broker-impl&revision=2013-10-28 +urn:ietf:params:xml:ns:yang:ietf-inet-types?module=ietf-inet-types&revision=2010-09-24 +urn:ietf:params:netconf:capability:rollback-on-error:1.0 +urn:ietf:params:xml:ns:yang:ietf-yang-types?module=ietf-yang-types&revision=2010-09-24 +urn:opendaylight:params:xml:ns:yang:controller:threadpool:impl?module=threadpool-impl&revision=2013-04-05 +urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:impl?module=opendaylight-sal-dom-broker-impl&revision=2013-10-28 +urn:opendaylight:params:xml:ns:yang:controller:logback:config?module=config-logging&revision=2013-07-16 +urn:opendaylight:yang:extension:yang-ext?module=yang-ext&revision=2013-07-09 +urn:opendaylight:params:xml:ns:yang:iana?module=iana&revision=2013-08-16 +urn:opendaylight:params:xml:ns:yang:controller:md:sal:common?module=opendaylight-md-sal-common&revision=2013-10-28 +urn:opendaylight:params:xml:ns:yang:ieee754?module=ieee754&revision=2013-08-19 diff --git a/opendaylight/config/config-persister-directory-adapter/src/test/resources/expectedSnapshot.xml b/opendaylight/config/config-persister-directory-adapter/src/test/resources/expectedSnapshot.xml new file mode 100644 index 0000000000..a6a57d704a --- /dev/null +++ b/opendaylight/config/config-persister-directory-adapter/src/test/resources/expectedSnapshot.xml @@ -0,0 +1,103 @@ + + + + prefix:schema-service-singleton + yang-schema-service + + + prefix:hash-map-data-store + hash-map-data-store + + + prefix:dom-broker-impl + dom-broker + + dom:dom-data-store + ref_hash-map-data-store + + + + prefix:binding-broker-impl + binding-broker-impl + + binding:binding-notification-service + ref_binding-notification-broker + + + binding:binding-data-broker + ref_binding-data-broker + + + + prefix:runtime-generated-mapping + runtime-mapping-singleton + + + prefix:binding-notification-broker + binding-notification-broker + + + prefix:binding-data-broker + binding-data-broker + + dom:dom-broker-osgi-registry + ref_dom-broker + + + binding:binding-dom-mapping-service + ref_runtime-mapping-singleton + + + + + + dom:schema-service + + ref_yang-schema-service + /config/modules/module[name='schema-service-singleton']/instance[name='yang-schema-service'] + + + + binding:binding-notification-service + + ref_binding-notification-broker + /config/modules/module[name='binding-notification-broker']/instance[name='binding-notification-broker'] + + + + dom:dom-data-store + + ref_hash-map-data-store + /config/modules/module[name='hash-map-data-store']/instance[name='hash-map-data-store'] + + + + binding:binding-broker-osgi-registry + + ref_binding-broker-impl + /config/modules/module[name='binding-broker-impl']/instance[name='binding-broker-impl'] + + + + binding-impl:binding-dom-mapping-service + + ref_runtime-mapping-singleton + /config/modules/module[name='runtime-generated-mapping']/instance[name='runtime-mapping-singleton'] + + + + dom:dom-broker-osgi-registry + + ref_dom-broker + /config/modules/module[name='dom-broker-impl']/instance[name='dom-broker'] + + + + binding:binding-data-broker + + ref_binding-data-broker + /config/modules/module[name='binding-data-broker']/instance[name='binding-data-broker'] + + + + diff --git a/opendaylight/config/config-persister-directory-adapter/src/test/resources/oneFile/controller.config.txt b/opendaylight/config/config-persister-directory-adapter/src/test/resources/oneFile/controller.config.txt new file mode 100644 index 0000000000..99b4cb9891 --- /dev/null +++ b/opendaylight/config/config-persister-directory-adapter/src/test/resources/oneFile/controller.config.txt @@ -0,0 +1,120 @@ +//MODULES START + + prefix:schema-service-singleton + yang-schema-service + + + prefix:hash-map-data-store + hash-map-data-store + + + prefix:dom-broker-impl + dom-broker + + dom:dom-data-store + ref_hash-map-data-store + + + + prefix:binding-broker-impl + binding-broker-impl + + binding:binding-notification-service + ref_binding-notification-broker + + + binding:binding-data-broker + ref_binding-data-broker + + + + prefix:runtime-generated-mapping + runtime-mapping-singleton + + + prefix:binding-notification-broker + binding-notification-broker + + + prefix:binding-data-broker + binding-data-broker + + dom:dom-broker-osgi-registry + ref_dom-broker + + + binding:binding-dom-mapping-service + ref_runtime-mapping-singleton + + +//SERVICES START + + dom:schema-service + + ref_yang-schema-service + /config/modules/module[name='schema-service-singleton']/instance[name='yang-schema-service'] + + + + binding:binding-notification-service + + ref_binding-notification-broker + /config/modules/module[name='binding-notification-broker']/instance[name='binding-notification-broker'] + + + + dom:dom-data-store + + ref_hash-map-data-store + /config/modules/module[name='hash-map-data-store']/instance[name='hash-map-data-store'] + + + + binding:binding-broker-osgi-registry + + ref_binding-broker-impl + /config/modules/module[name='binding-broker-impl']/instance[name='binding-broker-impl'] + + + + binding-impl:binding-dom-mapping-service + + ref_runtime-mapping-singleton + /config/modules/module[name='runtime-generated-mapping']/instance[name='runtime-mapping-singleton'] + + + + dom:dom-broker-osgi-registry + + ref_dom-broker + /config/modules/module[name='dom-broker-impl']/instance[name='dom-broker'] + + + + binding:binding-data-broker + + ref_binding-data-broker + /config/modules/module[name='binding-data-broker']/instance[name='binding-data-broker'] + + +//CAPABILITIES START +urn:opendaylight:l2:types?module=opendaylight-l2-types&revision=2013-08-27 +urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding?module=opendaylight-md-sal-binding&revision=2013-10-28 +urn:opendaylight:params:xml:ns:yang:controller:threadpool?module=threadpool&revision=2013-04-09 +urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom?module=opendaylight-md-sal-dom&revision=2013-10-28 +urn:opendaylight:params:xml:ns:yang:controller:config?module=config&revision=2013-04-05 +urn:ietf:params:netconf:capability:candidate:1.0 +urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring?module=ietf-netconf-monitoring&revision=2010-10-04 +urn:opendaylight:params:xml:ns:yang:controller:netty:eventexecutor?module=netty-event-executor&revision=2013-11-12 +urn:ietf:params:xml:ns:yang:rpc-context?module=rpc-context&revision=2013-06-17 +urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding:impl?module=opendaylight-sal-binding-broker-impl&revision=2013-10-28 +urn:ietf:params:xml:ns:yang:ietf-inet-types?module=ietf-inet-types&revision=2010-09-24 +urn:ietf:params:netconf:capability:rollback-on-error:1.0 +urn:ietf:params:xml:ns:yang:ietf-yang-types?module=ietf-yang-types&revision=2010-09-24 +urn:opendaylight:params:xml:ns:yang:controller:threadpool:impl?module=threadpool-impl&revision=2013-04-05 +urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:impl?module=opendaylight-sal-dom-broker-impl&revision=2013-10-28 +urn:opendaylight:params:xml:ns:yang:controller:logback:config?module=config-logging&revision=2013-07-16 +urn:opendaylight:yang:extension:yang-ext?module=yang-ext&revision=2013-07-09 +urn:opendaylight:params:xml:ns:yang:iana?module=iana&revision=2013-08-16 +urn:opendaylight:params:xml:ns:yang:controller:md:sal:common?module=opendaylight-md-sal-common&revision=2013-10-28 +urn:opendaylight:params:xml:ns:yang:ieee754?module=ieee754&revision=2013-08-19 diff --git a/opendaylight/config/config-persister-directory-adapter/src/test/resources/twoFiles/controller.config1.txt b/opendaylight/config/config-persister-directory-adapter/src/test/resources/twoFiles/controller.config1.txt new file mode 100644 index 0000000000..6ae48fb84b --- /dev/null +++ b/opendaylight/config/config-persister-directory-adapter/src/test/resources/twoFiles/controller.config1.txt @@ -0,0 +1,82 @@ +//MODULES START + + prefix:schema-service-singleton + yang-schema-service + + + prefix:hash-map-data-store + hash-map-data-store + + + prefix:dom-broker-impl + dom-broker + + dom:dom-data-store + ref_hash-map-data-store + + + + prefix:binding-broker-impl + binding-broker-impl + + binding:binding-notification-service + ref_binding-notification-broker + + + binding:binding-data-broker + ref_binding-data-broker + + + + prefix:runtime-generated-mapping + runtime-mapping-singleton + + + prefix:binding-notification-broker + binding-notification-broker + +//SERVICES START + + dom:schema-service + + ref_yang-schema-service + /config/modules/module[name='schema-service-singleton']/instance[name='yang-schema-service'] + + + + binding:binding-notification-service + + ref_binding-notification-broker + /config/modules/module[name='binding-notification-broker']/instance[name='binding-notification-broker'] + + + + dom:dom-data-store + + ref_hash-map-data-store + /config/modules/module[name='hash-map-data-store']/instance[name='hash-map-data-store'] + + + + binding:binding-broker-osgi-registry + + ref_binding-broker-impl + /config/modules/module[name='binding-broker-impl']/instance[name='binding-broker-impl'] + + + + binding-impl:binding-dom-mapping-service + + ref_runtime-mapping-singleton + /config/modules/module[name='runtime-generated-mapping']/instance[name='runtime-mapping-singleton'] + + + + dom:dom-broker-osgi-registry + + ref_dom-broker + /config/modules/module[name='dom-broker-impl']/instance[name='dom-broker'] + + +//CAPABILITIES START +urn:opendaylight:l2:types?module=opendaylight-l2-types&revision=2013-08-27 diff --git a/opendaylight/config/config-persister-directory-adapter/src/test/resources/twoFiles/controller.config2.txt b/opendaylight/config/config-persister-directory-adapter/src/test/resources/twoFiles/controller.config2.txt new file mode 100644 index 0000000000..ad9138f5d2 --- /dev/null +++ b/opendaylight/config/config-persister-directory-adapter/src/test/resources/twoFiles/controller.config2.txt @@ -0,0 +1,41 @@ +//MODULES START + + prefix:binding-data-broker + binding-data-broker + + dom:dom-broker-osgi-registry + ref_dom-broker + + + binding:binding-dom-mapping-service + ref_runtime-mapping-singleton + + +//SERVICES START + + binding:binding-data-broker + + ref_binding-data-broker + /config/modules/module[name='binding-data-broker']/instance[name='binding-data-broker'] + + +//CAPABILITIES START +urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding?module=opendaylight-md-sal-binding&revision=2013-10-28 +urn:opendaylight:params:xml:ns:yang:controller:threadpool?module=threadpool&revision=2013-04-09 +urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom?module=opendaylight-md-sal-dom&revision=2013-10-28 +urn:opendaylight:params:xml:ns:yang:controller:config?module=config&revision=2013-04-05 +urn:ietf:params:netconf:capability:candidate:1.0 +urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring?module=ietf-netconf-monitoring&revision=2010-10-04 +urn:opendaylight:params:xml:ns:yang:controller:netty:eventexecutor?module=netty-event-executor&revision=2013-11-12 +urn:ietf:params:xml:ns:yang:rpc-context?module=rpc-context&revision=2013-06-17 +urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding:impl?module=opendaylight-sal-binding-broker-impl&revision=2013-10-28 +urn:ietf:params:xml:ns:yang:ietf-inet-types?module=ietf-inet-types&revision=2010-09-24 +urn:ietf:params:netconf:capability:rollback-on-error:1.0 +urn:ietf:params:xml:ns:yang:ietf-yang-types?module=ietf-yang-types&revision=2010-09-24 +urn:opendaylight:params:xml:ns:yang:controller:threadpool:impl?module=threadpool-impl&revision=2013-04-05 +urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:impl?module=opendaylight-sal-dom-broker-impl&revision=2013-10-28 +urn:opendaylight:params:xml:ns:yang:controller:logback:config?module=config-logging&revision=2013-07-16 +urn:opendaylight:yang:extension:yang-ext?module=yang-ext&revision=2013-07-09 +urn:opendaylight:params:xml:ns:yang:iana?module=iana&revision=2013-08-16 +urn:opendaylight:params:xml:ns:yang:controller:md:sal:common?module=opendaylight-md-sal-common&revision=2013-10-28 +urn:opendaylight:params:xml:ns:yang:ieee754?module=ieee754&revision=2013-08-19 diff --git a/opendaylight/config/config-persister-file-adapter/pom.xml b/opendaylight/config/config-persister-file-adapter/pom.xml index b243eadcc6..d34dc37707 100644 --- a/opendaylight/config/config-persister-file-adapter/pom.xml +++ b/opendaylight/config/config-persister-file-adapter/pom.xml @@ -80,7 +80,6 @@ javax.xml.transform.stream, org.apache.commons.lang3, org.opendaylight.controller.config.persist.api, - 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 index 775fb1f881..66d0414d9a 100644 --- 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 @@ -12,11 +12,12 @@ 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.osgi.framework.BundleContext; +import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder; +import org.opendaylight.controller.config.persist.api.Persister; +import org.opendaylight.controller.config.persist.api.PropertiesProvider; +import org.opendaylight.controller.config.persist.api.StorageAdapter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.SAXException; @@ -26,20 +27,22 @@ import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; /** * StorageAdapter that stores configuration in a plan file. */ -public class FileStorageAdapter implements StorageAdapter { +public class FileStorageAdapter implements StorageAdapter, Persister { 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); @@ -55,8 +58,8 @@ public class FileStorageAdapter implements StorageAdapter { private File storage; @Override - public void setProperties(BundleContext bundleContext) { - File storage = extractStorageFileFromProperties(bundleContext); + public Persister instantiate(PropertiesProvider propertiesProvider) { + File storage = extractStorageFileFromProperties(propertiesProvider); logger.debug("Using file {}", storage.getAbsolutePath()); // Create file if it does not exist File parentFile = storage.getAbsoluteFile().getParentFile(); @@ -79,7 +82,7 @@ public class FileStorageAdapter implements StorageAdapter { + " property should be either set to positive value, or ommited. Can not be set to 0."); } setFileStorage(storage); - + return this; } @VisibleForTesting @@ -92,12 +95,11 @@ public class FileStorageAdapter implements StorageAdapter { numberOfStoredBackups = numberOfBackups; } - private static File extractStorageFileFromProperties(BundleContext bundleContext) { - String fileStorageProperty = bundleContext.getProperty(FILE_STORAGE_PROP); - Preconditions.checkNotNull(fileStorageProperty, "Unable to find " + FILE_STORAGE_PROP - + " in received context :" + bundleContext); + private static File extractStorageFileFromProperties(PropertiesProvider propertiesProvider) { + String fileStorageProperty = propertiesProvider.getProperty(FILE_STORAGE_PROP); + Preconditions.checkNotNull(fileStorageProperty, "Unable to find " + propertiesProvider.getFullKeyForReporting(FILE_STORAGE_PROP)); File result = new File(fileStorageProperty); - String numberOfBAckupsAsString = bundleContext.getProperty(NUMBER_OF_BACKUPS); + String numberOfBAckupsAsString = propertiesProvider.getProperty(NUMBER_OF_BACKUPS); if (numberOfBAckupsAsString != null) { numberOfStoredBackups = Integer.valueOf(numberOfBAckupsAsString); } else { @@ -189,7 +191,7 @@ public class FileStorageAdapter implements StorageAdapter { private boolean inLastConfig, inLastSnapshot; private final StringBuffer snapshotBuffer = new StringBuffer(); - private final Set caps = Sets.newHashSet(); + private final SortedSet caps = new TreeSet<>(); @Override public String getResult() { @@ -233,14 +235,14 @@ public class FileStorageAdapter implements StorageAdapter { return Optional.of(xmlContent); } - Set getCapabilities() throws IOException, SAXException, ParserConfigurationException { + SortedSet getCapabilities() throws IOException, SAXException, ParserConfigurationException { return caps; } } @Override - public void close() throws IOException { + public void close() { } @@ -252,9 +254,9 @@ public class FileStorageAdapter implements StorageAdapter { private class PersistedConfigImpl implements ConfigSnapshotHolder { private final String snapshot; - private final Set caps; + private final SortedSet caps; - public PersistedConfigImpl(Optional configSnapshot, Set capabilities) { + public PersistedConfigImpl(Optional configSnapshot, SortedSet capabilities) { this.snapshot = configSnapshot.get(); this.caps = capabilities; } @@ -265,7 +267,7 @@ public class FileStorageAdapter implements StorageAdapter { } @Override - public Set getCapabilities() { + public SortedSet 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 index c7d37dcbd8..ed50184aa7 100644 --- 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 @@ -12,17 +12,16 @@ 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 org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder; import java.io.File; import java.nio.file.Files; import java.util.Collection; -import java.util.Collections; -import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; @@ -49,14 +48,14 @@ public class FileStorageAdapterTest { FileStorageAdapter storage = new FileStorageAdapter(); storage.setFileStorage(file); storage.setNumberOfBackups(Integer.MAX_VALUE); - final Persister.ConfigSnapshotHolder holder = new Persister.ConfigSnapshotHolder() { + final ConfigSnapshotHolder holder = new ConfigSnapshotHolder() { @Override public String getConfigSnapshot() { return createConfig(); } @Override - public Set getCapabilities() { + public SortedSet getCapabilities() { return createCaps(); } }; @@ -76,15 +75,15 @@ public class FileStorageAdapterTest { }); assertEquals(14, readLines.size()); - Optional lastConf = storage.loadLastConfig(); + 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(); + private SortedSet createCaps() { + SortedSet caps = new TreeSet<>(); caps.add("cap1"); caps.add("cap2"); @@ -97,14 +96,14 @@ public class FileStorageAdapterTest { FileStorageAdapter storage = new FileStorageAdapter(); storage.setFileStorage(file); storage.setNumberOfBackups(1); - final Persister.ConfigSnapshotHolder holder = new Persister.ConfigSnapshotHolder() { + final ConfigSnapshotHolder holder = new ConfigSnapshotHolder() { @Override public String getConfigSnapshot() { return createConfig(); } @Override - public Set getCapabilities() { + public SortedSet getCapabilities() { return createCaps(); } }; @@ -124,7 +123,7 @@ public class FileStorageAdapterTest { }); assertEquals(7, readLines.size()); - Optional lastConf = storage.loadLastConfig(); + Optional lastConf = storage.loadLastConfig(); assertTrue(lastConf.isPresent()); assertEquals("2", lastConf.get().getConfigSnapshot().replaceAll("\\s", "")); @@ -135,14 +134,14 @@ public class FileStorageAdapterTest { FileStorageAdapter storage = new FileStorageAdapter(); storage.setFileStorage(file); storage.setNumberOfBackups(2); - final Persister.ConfigSnapshotHolder holder = new Persister.ConfigSnapshotHolder() { + final ConfigSnapshotHolder holder = new ConfigSnapshotHolder() { @Override public String getConfigSnapshot() { return createConfig(); } @Override - public Set getCapabilities() { + public SortedSet getCapabilities() { return createCaps(); } }; @@ -164,7 +163,7 @@ public class FileStorageAdapterTest { assertEquals(14, readLines.size()); - Optional lastConf = storage.loadLastConfig(); + Optional lastConf = storage.loadLastConfig(); assertTrue(lastConf.isPresent()); assertEquals("3", lastConf.get().getConfigSnapshot().replaceAll("\\s", "")); @@ -179,7 +178,7 @@ public class FileStorageAdapterTest { FileStorageAdapter storage = new FileStorageAdapter(); storage.setFileStorage(file); - Optional elementOptional = storage.loadLastConfig(); + Optional elementOptional = storage.loadLastConfig(); assertThat(elementOptional.isPresent(), is(false)); } @@ -192,15 +191,15 @@ public class FileStorageAdapterTest { @Test(expected = NullPointerException.class) public void testNoProperties2() throws Exception { FileStorageAdapter storage = new FileStorageAdapter(); - storage.persistConfig(new Persister.ConfigSnapshotHolder() { + storage.persistConfig(new ConfigSnapshotHolder() { @Override public String getConfigSnapshot() { return Mockito.mock(String.class); } @Override - public Set getCapabilities() { - return Collections. emptySet(); + public SortedSet getCapabilities() { + return new TreeSet<>(); } } ); } diff --git a/opendaylight/config/logback-config/src/main/java/org/opendaylight/controller/config/yang/logback/config/ContextSetterImpl.java b/opendaylight/config/logback-config/src/main/java/org/opendaylight/controller/config/yang/logback/config/ContextSetterImpl.java index 205f123b1d..02fba141b3 100644 --- a/opendaylight/config/logback-config/src/main/java/org/opendaylight/controller/config/yang/logback/config/ContextSetterImpl.java +++ b/opendaylight/config/logback-config/src/main/java/org/opendaylight/controller/config/yang/logback/config/ContextSetterImpl.java @@ -101,13 +101,15 @@ public class ContextSetterImpl implements ContextSetter, Closeable { private void addNewAppenders(Map> appendersMap, LoggerTO logger, ch.qos.logback.classic.Logger logbackLogger, Optional>> appendersBefore) { - for (String appenderName : logger.getAppenders()) { - if (appendersMap.containsKey(appenderName)) { - logbackLogger.addAppender(appendersMap.get(appenderName)); - classLogger.trace("Logger {}: Adding new appender: {}", logger.getLoggerName(), appenderName); - } else { - throw new IllegalStateException("No appender " + appenderName - + " found. This error should have been discovered by validation"); + if (logger.getAppenders() != null) { + for (String appenderName : logger.getAppenders()) { + if (appendersMap.containsKey(appenderName)) { + logbackLogger.addAppender(appendersMap.get(appenderName)); + classLogger.trace("Logger {}: Adding new appender: {}", logger.getLoggerName(), appenderName); + } else { + throw new IllegalStateException("No appender " + appenderName + + " found. This error should have been discovered by validation"); + } } } } diff --git a/opendaylight/config/logback-config/src/main/java/org/opendaylight/controller/config/yang/logback/config/LogbackModule.java b/opendaylight/config/logback-config/src/main/java/org/opendaylight/controller/config/yang/logback/config/LogbackModule.java index 9cbcd47b20..d75bf6367a 100644 --- a/opendaylight/config/logback-config/src/main/java/org/opendaylight/controller/config/yang/logback/config/LogbackModule.java +++ b/opendaylight/config/logback-config/src/main/java/org/opendaylight/controller/config/yang/logback/config/LogbackModule.java @@ -54,11 +54,12 @@ public final class LogbackModule extends org.opendaylight.controller.config.yang "LoggerName needs to be set", loggersJmxAttribute); JmxAttributeValidationException.checkCondition(!loggerToValidate.getLevel().isEmpty(), "Level needs to be set", loggersJmxAttribute); - - for (String appenderName : loggerToValidate.getAppenders()) { - JmxAttributeValidationException.checkCondition(appenderNames.contains(appenderName), "Appender " - + appenderName + " referenced by logger " + loggerToValidate.getLoggerName() - + " not present in configuration, present appenders: " + appenderNames, loggersJmxAttribute); + if (loggerToValidate.getAppenders() != null) { + for (String appenderName : loggerToValidate.getAppenders()) { + JmxAttributeValidationException.checkCondition(appenderNames.contains(appenderName), "Appender " + + appenderName + " referenced by logger " + loggerToValidate.getLoggerName() + + " not present in configuration, present appenders: " + appenderNames, loggersJmxAttribute); + } } } diff --git a/opendaylight/config/pom.xml b/opendaylight/config/pom.xml index 22be6f162b..c1eef701da 100644 --- a/opendaylight/config/pom.xml +++ b/opendaylight/config/pom.xml @@ -35,6 +35,7 @@ netty-threadgroup-config netty-event-executor-config netty-timer-config + config-persister-directory-adapter diff --git a/opendaylight/distribution/opendaylight/pom.xml b/opendaylight/distribution/opendaylight/pom.xml index 5f2651659d..d1b1e73636 100644 --- a/opendaylight/distribution/opendaylight/pom.xml +++ b/opendaylight/distribution/opendaylight/pom.xml @@ -216,7 +216,11 @@ config-persister-file-adapter ${config.version} - + + org.opendaylight.controller + config-persister-directory-adapter + ${config.version} + diff --git a/opendaylight/distribution/opendaylight/src/main/resources/configuration/config.ini b/opendaylight/distribution/opendaylight/src/main/resources/configuration/config.ini index 00c46d9529..b5bdb9e903 100644 --- a/opendaylight/distribution/opendaylight/src/main/resources/configuration/config.ini +++ b/opendaylight/distribution/opendaylight/src/main/resources/configuration/config.ini @@ -17,13 +17,19 @@ osgi.bundles=\ netconf.tcp.address=0.0.0.0 netconf.tcp.port=8383 - netconf.ssh.address=0.0.0.0 netconf.ssh.port=1830 -netconf.config.persister.storageAdapterClass=org.opendaylight.controller.config.persist.storage.file.FileStorageAdapter -fileStorage=configuration/controller.config -numberOfBackups=1 +netconf.config.persister.active=1,2 +# read startup configuration +netconf.config.persister.1.storageAdapterClass=org.opendaylight.controller.config.persist.storage.directory.DirectoryStorageAdapter +netconf.config.persister.1.properties.directoryStorage=configuration/initial/ +netconf.config.persister.1.readonly=true + +netconf.config.persister.2.storageAdapterClass=org.opendaylight.controller.config.persist.storage.file.FileStorageAdapter +netconf.config.persister.2.properties.fileStorage=configuration/current/controller.currentconfig.txt + + yangstore.blacklist=.*controller.model.* # Set Default start level for framework diff --git a/opendaylight/distribution/opendaylight/src/main/resources/configuration/initial/controller-config.1.txt b/opendaylight/distribution/opendaylight/src/main/resources/configuration/initial/controller-config.1.txt new file mode 100644 index 0000000000..689b45ad3b --- /dev/null +++ b/opendaylight/distribution/opendaylight/src/main/resources/configuration/initial/controller-config.1.txt @@ -0,0 +1,5 @@ +//MODULES START +//SERVICES START + +//CAPABILITIES START +urn:opendaylight:params:xml:ns:yang:controller:logback:config?module=config-logging&revision=2013-07-16 diff --git a/opendaylight/distribution/opendaylight/src/main/resources/configuration/controller.config b/opendaylight/distribution/opendaylight/src/main/resources/configuration/initial/controller-config.2.txt similarity index 94% rename from opendaylight/distribution/opendaylight/src/main/resources/configuration/controller.config rename to opendaylight/distribution/opendaylight/src/main/resources/configuration/initial/controller-config.2.txt index 947c15b35c..acae8866ce 100644 --- a/opendaylight/distribution/opendaylight/src/main/resources/configuration/controller.config +++ b/opendaylight/distribution/opendaylight/src/main/resources/configuration/initial/controller-config.2.txt @@ -1,6 +1,4 @@ -//START OF CONFIG-LAST - - +//MODULES START prefix:schema-service-singleton yang-schema-service @@ -49,8 +47,7 @@ ref_runtime-mapping-singleton - - +//SERVICES START dom:schema-service @@ -100,10 +97,7 @@ /config/modules/module[name='binding-data-broker']/instance[name='binding-data-broker'] - - - -//END OF SNAPSHOT +//CAPABILITIES START urn:opendaylight:l2:types?module=opendaylight-l2-types&revision=2013-08-27 urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding?module=opendaylight-md-sal-binding&revision=2013-10-28 urn:opendaylight:params:xml:ns:yang:controller:threadpool?module=threadpool&revision=2013-04-09 @@ -119,9 +113,7 @@ urn:ietf:params:netconf:capability:rollback-on-error:1.0 urn:ietf:params:xml:ns:yang:ietf-yang-types?module=ietf-yang-types&revision=2010-09-24 urn:opendaylight:params:xml:ns:yang:controller:threadpool:impl?module=threadpool-impl&revision=2013-04-05 urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:impl?module=opendaylight-sal-dom-broker-impl&revision=2013-10-28 -urn:opendaylight:params:xml:ns:yang:controller:logback:config?module=config-logging&revision=2013-07-16 urn:opendaylight:yang:extension:yang-ext?module=yang-ext&revision=2013-07-09 urn:opendaylight:params:xml:ns:yang:iana?module=iana&revision=2013-08-16 urn:opendaylight:params:xml:ns:yang:controller:md:sal:common?module=opendaylight-md-sal-common&revision=2013-10-28 urn:opendaylight:params:xml:ns:yang:ieee754?module=ieee754&revision=2013-08-19 -//END OF CONFIG diff --git a/opendaylight/distribution/opendaylight/src/main/resources/configuration/logback.xml b/opendaylight/distribution/opendaylight/src/main/resources/configuration/logback.xml index 3ad0c61d4f..2815422274 100644 --- a/opendaylight/distribution/opendaylight/src/main/resources/configuration/logback.xml +++ b/opendaylight/distribution/opendaylight/src/main/resources/configuration/logback.xml @@ -36,7 +36,6 @@ - diff --git a/opendaylight/md-sal/model/pom.xml b/opendaylight/md-sal/model/pom.xml index 34ad23844f..2f3b7a036c 100644 --- a/opendaylight/md-sal/model/pom.xml +++ b/opendaylight/md-sal/model/pom.xml @@ -40,6 +40,7 @@ ${project.groupId}.${project.artifactId} + *,org.opendaylight.yangtools.yang.binding.annotations diff --git a/opendaylight/md-sal/sal-binding-it/src/main/java/org/opendaylight/controller/test/sal/binding/it/TestHelper.java b/opendaylight/md-sal/sal-binding-it/src/main/java/org/opendaylight/controller/test/sal/binding/it/TestHelper.java index 0eace4daac..18c93ec6d9 100644 --- a/opendaylight/md-sal/sal-binding-it/src/main/java/org/opendaylight/controller/test/sal/binding/it/TestHelper.java +++ b/opendaylight/md-sal/sal-binding-it/src/main/java/org/opendaylight/controller/test/sal/binding/it/TestHelper.java @@ -41,7 +41,7 @@ public class TestHelper { mavenBundle("org.opendaylight.bgpcep", "framework").versionAsInProject(), // mavenBundle("org.opendaylight.bgpcep", "util").versionAsInProject(), // mavenBundle("commons-codec", "commons-codec").versionAsInProject(), - + mavenBundle(CONTROLLER, "config-api").versionAsInProject(), // // mavenBundle(CONTROLLER, "config-manager").versionAsInProject(), // // mavenBundle("commons-io", "commons-io").versionAsInProject(), // @@ -54,30 +54,30 @@ public class TestHelper { mavenBundle(CONTROLLER, "logback-config").versionAsInProject(), // mavenBundle(CONTROLLER, "config-persister-api").versionAsInProject(), // mavenBundle(CONTROLLER, "netconf-api").versionAsInProject(), // - + mavenBundle(CONTROLLER, "netconf-client").versionAsInProject(), // mavenBundle(CONTROLLER, "netconf-util").versionAsInProject(), // mavenBundle(CONTROLLER + ".thirdparty", "ganymed", "1.0-SNAPSHOT"), // mavenBundle(CONTROLLER, "netconf-mapping-api").versionAsInProject(), // mavenBundle(CONTROLLER, "config-persister-impl").versionAsInProject(), // - + mavenBundle("io.netty", "netty-handler").versionAsInProject(), // mavenBundle("io.netty", "netty-codec").versionAsInProject(), // mavenBundle("io.netty", "netty-buffer").versionAsInProject(), // mavenBundle("io.netty", "netty-transport").versionAsInProject(), // mavenBundle("io.netty", "netty-common").versionAsInProject(), // - + mavenBundle("org.opendaylight.controller.thirdparty", "exificient", "0.9.2-SNAPSHOT"), // - + mavenBundle("org.apache.servicemix.bundles", "org.apache.servicemix.bundles.xerces", "2.11.0_1"), mavenBundle("org.eclipse.birt.runtime.3_7_1", "org.apache.xml.resolver", "1.2.0"), - + mavenBundle(CONTROLLER, "config-netconf-connector").versionAsInProject(), // mavenBundle(CONTROLLER, "netconf-impl").versionAsInProject(), // - + mavenBundle(CONTROLLER, "config-persister-file-adapter").versionAsInProject().noStart()); - + } public static Option bindingAwareSalBundles() { @@ -112,10 +112,12 @@ public class TestHelper { systemProperty("netconf.tcp.address").value("0.0.0.0"), // systemProperty("netconf.tcp.port").value("18383"), // - systemProperty("netconf.config.persister.storageAdapterClass").value( + systemProperty("netconf.config.persister.active").value("1"), // + systemProperty("netconf.config.persister.1.storageAdapterClass").value( "org.opendaylight.controller.config.persist.storage.file.FileStorageAdapter"), // - systemProperty("fileStorage").value(PathUtils.getBaseDir() + "/src/test/resources/controller.config"), // - systemProperty("numberOfBackups").value("1") // + systemProperty("netconf.config.persister.1.properties.fileStorage") + .value(PathUtils.getBaseDir() + "/src/test/resources/controller.config"), // + systemProperty("netconf.config.persister.1.properties.numberOfBackups").value("1") // //systemProperty("yangstore.blacklist").value(".*controller.model.*") // ); @@ -170,7 +172,7 @@ public class TestHelper { * org/mockito/cglib/proxy/Factory have different Class objects * for the type org/mockito/cglib/ proxy/Callback used in the * signature - * + * * So we disable the bootdelegation. this property has no effect * on the other OSGi implementation. */ diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java index 2a556c9be4..1275924614 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java @@ -17,6 +17,9 @@ import java.net.InetSocketAddress; import javax.net.ssl.SSLContext; import org.opendaylight.controller.netconf.client.NetconfClientDispatcher; +import org.opendaylight.controller.netconf.client.NetconfSshClientDispatcher; +import org.opendaylight.controller.netconf.util.handler.ssh.authentication.AuthenticationHandler; +import org.opendaylight.controller.netconf.util.handler.ssh.authentication.LoginPassword; import org.opendaylight.controller.sal.connect.netconf.NetconfDevice; import org.osgi.framework.BundleContext; @@ -75,8 +78,13 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co EventLoopGroup bossGroup = getBossThreadGroupDependency(); EventLoopGroup workerGroup = getWorkerThreadGroupDependency(); Optional maybeContext = Optional.absent(); - NetconfClientDispatcher dispatcher = new NetconfClientDispatcher(maybeContext , bossGroup, workerGroup); - + NetconfClientDispatcher dispatcher = null; + if(getTcpOnly()) { + dispatcher = new NetconfClientDispatcher(maybeContext , bossGroup, workerGroup); + } else { + AuthenticationHandler authHandler = new LoginPassword(getUsername(),getPassword()); + dispatcher = new NetconfSshClientDispatcher(authHandler , bossGroup, workerGroup); + } getDomRegistryDependency().registerProvider(device, bundleContext); device.start(dispatcher); diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang b/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang index 45f10162ca..9238514110 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang @@ -45,7 +45,18 @@ module odl-sal-netconf-connector-cfg { leaf port { type uint32; } + + leaf tcp-only { + type boolean; + } + leaf username { + type string; + } + + leaf password { + type string; + } container dom-registry { uses config:service-ref { refine type { diff --git a/opendaylight/netconf/config-persister-impl/pom.xml b/opendaylight/netconf/config-persister-impl/pom.xml index 21ecd31339..ce25de215e 100644 --- a/opendaylight/netconf/config-persister-impl/pom.xml +++ b/opendaylight/netconf/config-persister-impl/pom.xml @@ -53,6 +53,11 @@ ${bgpcep.version} test + + commons-io + commons-io + test + @@ -72,7 +77,6 @@ javax.management, javax.xml.parsers, org.opendaylight.controller.config.persist.api, - org.opendaylight.controller.config.persist.api.storage, org.opendaylight.controller.netconf.api, org.opendaylight.controller.netconf.api.jmx, org.opendaylight.controller.netconf.client, diff --git a/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/CapabilityStrippingConfigSnapshotHolder.java b/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/CapabilityStrippingConfigSnapshotHolder.java new file mode 100644 index 0000000000..d9c5dfaded --- /dev/null +++ b/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/CapabilityStrippingConfigSnapshotHolder.java @@ -0,0 +1,118 @@ +/* + * 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 org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder; +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.Attr; +import org.w3c.dom.Element; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.regex.Pattern; + +import static com.google.common.base.Preconditions.checkState; + +public class CapabilityStrippingConfigSnapshotHolder implements ConfigSnapshotHolder { + private static final Logger logger = LoggerFactory.getLogger(CapabilityStrippingConfigSnapshotHolder.class); + + private final String configSnapshot; + private final StripCapabilitiesResult stripCapabilitiesResult; + + public CapabilityStrippingConfigSnapshotHolder(Element snapshot, Set capabilities, Pattern ignoredMissingCapabilityRegex) { + final XmlElement configElement = XmlElement.fromDomElement(snapshot); + configSnapshot = XmlUtil.toString(configElement.getDomElement()); + stripCapabilitiesResult = stripCapabilities(configElement, capabilities, ignoredMissingCapabilityRegex); + } + + private static class StripCapabilitiesResult { + private final SortedSet requiredCapabilities, missingNamespaces; + + private StripCapabilitiesResult(SortedSet requiredCapabilities, SortedSet missingNamespaces) { + this.requiredCapabilities = Collections.unmodifiableSortedSet(requiredCapabilities); + this.missingNamespaces = Collections.unmodifiableSortedSet(missingNamespaces); + } + } + + + @VisibleForTesting + static StripCapabilitiesResult stripCapabilities(XmlElement configElement, Set allCapabilitiesFromHello, + Pattern ignoredMissingCapabilityRegex) { + // collect all namespaces + Set foundNamespacesInXML = getNamespaces(configElement); + logger.trace("All capabilities {}\nFound namespaces in XML {}", allCapabilitiesFromHello, foundNamespacesInXML); + // required are referenced both in xml and hello + SortedSet requiredCapabilities = new TreeSet<>(); + // can be removed + Set obsoleteCapabilities = new HashSet<>(); + // are in xml but not in hello + SortedSet missingNamespaces = new TreeSet<>(foundNamespacesInXML); + for (String capability : allCapabilitiesFromHello) { + String namespace = capability.replaceAll("\\?.*",""); + if (foundNamespacesInXML.contains(namespace)) { + requiredCapabilities.add(capability); + checkState(missingNamespaces.remove(namespace)); + } else { + obsoleteCapabilities.add(capability); + } + } + + logger.trace("Required capabilities {}, \nObsolete capabilities {}", + requiredCapabilities, obsoleteCapabilities); + + for(Iterator iterator = missingNamespaces.iterator();iterator.hasNext(); ){ + String capability = iterator.next(); + if (ignoredMissingCapabilityRegex.matcher(capability).matches()){ + logger.trace("Ignoring missing capability {}", capability); + iterator.remove(); + } + } + if (missingNamespaces.size() > 0) { + logger.warn("Some capabilities are missing: {}", missingNamespaces); + } + return new StripCapabilitiesResult(requiredCapabilities, missingNamespaces); + } + + static Set getNamespaces(XmlElement element){ + Set result = new HashSet<>(); + for (Entry attribute : element.getAttributes().entrySet()) { + if (attribute.getKey().startsWith("xmlns")){ + result.add(attribute.getValue().getValue()); + } + } + //element.getAttributes() + for(XmlElement child: element.getChildElements()) { + result.addAll(getNamespaces(child)); + } + return result; + } + + @Override + public SortedSet getCapabilities() { + return stripCapabilitiesResult.requiredCapabilities; + } + + @VisibleForTesting + Set getMissingNamespaces(){ + return stripCapabilitiesResult.missingNamespaces; + } + + @Override + public String getConfigSnapshot() { + return configSnapshot; + } +} 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 index 25d2ad6abd..a569f90538 100644 --- 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 @@ -13,6 +13,7 @@ import com.google.common.base.Preconditions; import com.google.common.collect.Sets; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; +import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder; import org.opendaylight.controller.config.persist.api.Persister; import org.opendaylight.controller.netconf.api.NetconfMessage; import org.opendaylight.controller.netconf.api.jmx.CommitJMXNotification; @@ -43,6 +44,7 @@ import java.net.InetSocketAddress; import java.util.Collections; import java.util.HashSet; import java.util.Set; +import java.util.regex.Pattern; /** * Responsible for listening for notifications from netconf containing latest @@ -53,6 +55,8 @@ import java.util.Set; public class ConfigPersisterNotificationHandler implements NotificationListener, Closeable { private static final Logger logger = LoggerFactory.getLogger(ConfigPersisterNotificationHandler.class); + private static final int NETCONF_SEND_ATTEMPT_MS_DELAY = 1000; + private static final int NETCONF_SEND_ATTEMPTS = 20; private final InetSocketAddress address; private final EventLoopGroup nettyThreadgroup; @@ -66,26 +70,29 @@ public class ConfigPersisterNotificationHandler implements NotificationListener, private final ObjectName on = DefaultCommitOperationMXBean.objectName; - public static final long DEFAULT_TIMEOUT = 40000L; + public static final long DEFAULT_TIMEOUT = 120000L;// 120 seconds until netconf must be stable private final long timeout; + private final Pattern ignoredMissingCapabilityRegex; public ConfigPersisterNotificationHandler(Persister persister, InetSocketAddress address, - MBeanServerConnection mbeanServer) { - this(persister, address, mbeanServer, DEFAULT_TIMEOUT); + MBeanServerConnection mbeanServer, Pattern ignoredMissingCapabilityRegex) { + this(persister, address, mbeanServer, DEFAULT_TIMEOUT, ignoredMissingCapabilityRegex); + } public ConfigPersisterNotificationHandler(Persister persister, InetSocketAddress address, - MBeanServerConnection mbeanServer, long timeout) { + MBeanServerConnection mbeanServer, long timeout, Pattern ignoredMissingCapabilityRegex) { this.persister = persister; this.address = address; this.mbeanServer = mbeanServer; this.timeout = timeout; this.nettyThreadgroup = new NioEventLoopGroup(); + this.ignoredMissingCapabilityRegex = ignoredMissingCapabilityRegex; } public void init() throws InterruptedException { - Optional maybeConfig = loadLastConfig(); + Optional maybeConfig = loadLastConfig(); if (maybeConfig.isPresent()) { logger.debug("Last config found {}", persister); @@ -93,6 +100,7 @@ public class ConfigPersisterNotificationHandler implements NotificationListener, registerToNetconf(maybeConfig.get().getCapabilities()); final String configSnapshot = maybeConfig.get().getConfigSnapshot(); + logger.trace("Pushing following xml to netconf {}", configSnapshot); try { pushLastConfig(XmlUtil.readXmlToElement(configSnapshot)); } catch (SAXException | IOException e) { @@ -219,26 +227,16 @@ public class ConfigPersisterNotificationHandler implements NotificationListener, 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(); - } - }); + persister.persistConfig(new CapabilityStrippingConfigSnapshotHolder(notification.getConfigSnapshot(), + notification.getCapabilities(), ignoredMissingCapabilityRegex)); logger.debug("Configuration persisted successfully"); } catch (IOException e) { throw new RuntimeException("Unable to persist configuration snapshot", e); } } - private Optional loadLastConfig() { - Optional maybeConfigElement; + private Optional loadLastConfig() { + Optional maybeConfigElement; try { maybeConfigElement = persister.loadLastConfig(); } catch (IOException e) { @@ -247,12 +245,15 @@ public class ConfigPersisterNotificationHandler implements NotificationListener, return maybeConfigElement; } - private synchronized void pushLastConfig(Element persistedConfig) { + private synchronized void pushLastConfig(Element xmlToBePersisted) { + logger.info("Pushing last configuration to netconf"); StringBuilder response = new StringBuilder("editConfig response = {"); - Element configElement = persistedConfig; - NetconfMessage message = createEditConfigMessage(configElement, "/netconfOp/editConfig.xml"); - NetconfMessage responseMessage = netconfClient.sendMessage(message); + + NetconfMessage message = createEditConfigMessage(xmlToBePersisted, "/netconfOp/editConfig.xml"); + + // sending message to netconf + NetconfMessage responseMessage = netconfClient.sendMessage(message, NETCONF_SEND_ATTEMPTS, NETCONF_SEND_ATTEMPT_MS_DELAY); XmlElement element = XmlElement.fromDomDocument(responseMessage.getDocument()); Preconditions.checkState(element.getName().equals(XmlNetconfConstants.RPC_REPLY_KEY)); @@ -261,7 +262,7 @@ public class ConfigPersisterNotificationHandler implements NotificationListener, checkIsOk(element, responseMessage); response.append(XmlUtil.toString(responseMessage.getDocument())); response.append("}"); - responseMessage = netconfClient.sendMessage(getNetconfMessageFromResource("/netconfOp/commit.xml")); + responseMessage = netconfClient.sendMessage(getNetconfMessageFromResource("/netconfOp/commit.xml"), NETCONF_SEND_ATTEMPTS, NETCONF_SEND_ATTEMPT_MS_DELAY); element = XmlElement.fromDomDocument(responseMessage.getDocument()); Preconditions.checkState(element.getName().equals(XmlNetconfConstants.RPC_REPLY_KEY)); @@ -271,7 +272,8 @@ public class ConfigPersisterNotificationHandler implements NotificationListener, response.append("commit response = {"); response.append(XmlUtil.toString(responseMessage.getDocument())); response.append("}"); - logger.debug("Last configuration loaded successfully"); + logger.info("Last configuration loaded successfully"); + logger.trace("Detailed message {}", response); } private void checkIsOk(XmlElement element, NetconfMessage responseMessage) { @@ -289,8 +291,8 @@ public class ConfigPersisterNotificationHandler implements NotificationListener, } } - private NetconfMessage createEditConfigMessage(Element dataElement, String editConfigResourcename) { - try (InputStream stream = getClass().getResourceAsStream(editConfigResourcename)) { + private static NetconfMessage createEditConfigMessage(Element dataElement, String editConfigResourcename) { + try (InputStream stream = ConfigPersisterNotificationHandler.class.getResourceAsStream(editConfigResourcename)) { Preconditions.checkNotNull(stream, "Unable to load resource " + editConfigResourcename); Document doc = XmlUtil.readXmlToDocument(stream); 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 index cd604312f4..b37c1457c3 100644 --- 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 @@ -9,19 +9,22 @@ package org.opendaylight.controller.netconf.persist.impl; import com.google.common.base.Optional; -import org.opendaylight.controller.config.persist.api.storage.StorageAdapter; -import org.osgi.framework.BundleContext; +import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder; +import org.opendaylight.controller.config.persist.api.Persister; +import org.opendaylight.controller.config.persist.api.PropertiesProvider; +import org.opendaylight.controller.config.persist.api.StorageAdapter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -public class NoOpStorageAdapter implements StorageAdapter { +public class NoOpStorageAdapter implements StorageAdapter, Persister { private static final Logger logger = LoggerFactory.getLogger(NoOpStorageAdapter.class); @Override - public void setProperties(BundleContext bundleContext) { - logger.debug("setProperties called with {}", bundleContext); + public Persister instantiate(PropertiesProvider propertiesProvider) { + logger.debug("instantiate called with {}", propertiesProvider); + return this; } @Override @@ -36,7 +39,7 @@ public class NoOpStorageAdapter implements StorageAdapter { } @Override - public void close() throws IOException { + public void close() { logger.debug("close called"); } } diff --git a/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/PersisterAggregator.java b/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/PersisterAggregator.java new file mode 100644 index 0000000000..7add448025 --- /dev/null +++ b/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/PersisterAggregator.java @@ -0,0 +1,193 @@ +/* + * 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.ConfigSnapshotHolder; +import org.opendaylight.controller.config.persist.api.Persister; +import org.opendaylight.controller.config.persist.api.StorageAdapter; +import org.opendaylight.controller.netconf.persist.impl.osgi.ConfigPersisterActivator; +import org.opendaylight.controller.netconf.persist.impl.osgi.PropertiesProviderBaseImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; + +/** + * {@link Persister} implementation that delegates persisting functionality to + * underlying {@link Persister} storages. Each storage has unique id, class, readonly value. + * + * 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. + * + * Example configuration:
+ netconf.config.persister.active=2,3
+ # read startup configuration
+ netconf.config.persister.1.storageAdapterClass=org.opendaylight.controller.config.persist.storage.directory.DirectoryStorageAdapter
+ netconf.config.persister.1.properties.fileStorage=configuration/initial/
+
+ netconf.config.persister.2.storageAdapterClass=org.opendaylight.controller.config.persist.storage.file.FileStorageAdapter
+ netconf.config.persister.2.readonly=true
+ netconf.config.persister.2.properties.fileStorage=configuration/current/controller.config.1.txt
+
+ netconf.config.persister.3.storageAdapterClass=org.opendaylight.controller.config.persist.storage.file.FileStorageAdapter
+ netconf.config.persister.3.properties.fileStorage=configuration/current/controller.config.2.txt
+ netconf.config.persister.3.properties.numberOfBackups=3
+
+ 
+ * During server startup {@link ConfigPersisterNotificationHandler} requests last snapshot from underlying storages. + * Each storage can respond by giving snapshot or absent response. + * The {@link #loadLastConfig()} will search for first non-absent response from storages ordered backwards as user + * specified (first '3', then '2'). + * + * When a commit notification is received, '2' will be omitted because readonly flag is set to true, so + * only '3' will have a chance to persist new configuration. If readonly was false or not specified, both storage adapters + * would be called in order specified by 'netconf.config.persister' property. + * + */ +public final class PersisterAggregator implements Persister { + private static final Logger logger = LoggerFactory.getLogger(PersisterAggregator.class); + + public static class PersisterWithConfiguration { + + public final Persister storage; + private final boolean readOnly; + + public PersisterWithConfiguration(Persister storage, boolean readOnly) { + this.storage = storage; + this.readOnly = readOnly; + } + + @Override + public String toString() { + return "PersisterWithConfiguration{" + + "storage=" + storage + + ", readOnly=" + readOnly + + '}'; + } + } + + private static PersisterWithConfiguration loadConfiguration(final String index, final PropertiesProviderBaseImpl propertiesProvider) { + + String classKey = index + "." + ConfigPersisterActivator.STORAGE_ADAPTER_CLASS_PROP_SUFFIX; + String storageAdapterClass = propertiesProvider.getProperty(classKey); + StorageAdapter storageAdapter; + if (storageAdapterClass == null || storageAdapterClass.equals("")) { + throw new IllegalStateException("No persister is defined in " + + propertiesProvider.getFullKeyForReporting(classKey) + + " property. Persister is not operational"); + } + + try { + Class clazz = Class.forName(storageAdapterClass); + boolean implementsCorrectIfc = StorageAdapter.class.isAssignableFrom(clazz); + if (implementsCorrectIfc == false) { + throw new IllegalArgumentException("Storage adapter " + clazz + " does not implement " + StorageAdapter.class); + } + storageAdapter = StorageAdapter.class.cast(clazz.newInstance()); + + boolean readOnly = false; + String readOnlyProperty = propertiesProvider.getProperty(index + "." + "readonly"); + if (readOnlyProperty != null && readOnlyProperty.equals("true")) { + readOnly = true; + } + + PropertiesProviderAdapterImpl innerProvider = new PropertiesProviderAdapterImpl(propertiesProvider, index); + Persister storage = storageAdapter.instantiate(innerProvider); + return new PersisterWithConfiguration(storage, readOnly); + } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { + throw new IllegalArgumentException("Unable to instantiate storage adapter from " + storageAdapterClass, e); + } + } + + /** + * Persisters ordered by 'netconf.config.persister' property. + */ + private final List persisterWithConfigurations; + + public PersisterAggregator(List persisterWithConfigurations) { + this.persisterWithConfigurations = persisterWithConfigurations; + + } + + public static PersisterAggregator createFromProperties(PropertiesProviderBaseImpl propertiesProvider) { + List persisterWithConfigurations = new ArrayList<>(); + String prefixes = propertiesProvider.getProperty("active"); + if (prefixes.isEmpty() == false) { + String [] keys = prefixes.split(","); + for (String index: keys) { + persisterWithConfigurations.add(PersisterAggregator.loadConfiguration(index, propertiesProvider)); + } + } + logger.debug("Initialized persister with following adapters {}", persisterWithConfigurations); + return new PersisterAggregator(persisterWithConfigurations); + } + + @Override + public void persistConfig(ConfigSnapshotHolder holder) throws IOException { + for (PersisterWithConfiguration persisterWithConfiguration: persisterWithConfigurations){ + if (!persisterWithConfiguration.readOnly){ + logger.debug("Calling {}.persistConfig",persisterWithConfiguration.storage); + persisterWithConfiguration.storage.persistConfig(holder); + } + } + } + + @Override + public Optional loadLastConfig() throws IOException { + // iterate in reverse order + ListIterator li = persisterWithConfigurations.listIterator(persisterWithConfigurations.size()); + while(li.hasPrevious()) { + PersisterWithConfiguration persisterWithConfiguration = li.previous(); + Optional configSnapshotHolderOptional = persisterWithConfiguration.storage.loadLastConfig(); + if (configSnapshotHolderOptional.isPresent()) { + return configSnapshotHolderOptional; + } + } + // no storage had an answer + return Optional.absent(); + } + + @VisibleForTesting + List getPersisterWithConfigurations() { + return persisterWithConfigurations; + } + + @Override + public void close() { + RuntimeException lastException = null; + for (PersisterWithConfiguration persisterWithConfiguration: persisterWithConfigurations){ + try{ + persisterWithConfiguration.storage.close(); + }catch(RuntimeException e) { + logger.error("Error while closing {}", persisterWithConfiguration.storage, e); + if (lastException == null){ + lastException = e; + } else { + lastException.addSuppressed(e); + } + } + } + if (lastException != null){ + throw lastException; + } + } + + @Override + public String toString() { + return "PersisterAggregator{" + + "persisterWithConfigurations=" + persisterWithConfigurations + + '}'; + } +} 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 deleted file mode 100644 index e06968e868..0000000000 --- a/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/PersisterImpl.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.controller.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.osgi.framework.BundleContext; - -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(BundleContext bundleContext) { - String storageAdapterClass = bundleContext.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(bundleContext); - - } 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/PropertiesProviderAdapterImpl.java b/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/PropertiesProviderAdapterImpl.java new file mode 100644 index 0000000000..981be827b7 --- /dev/null +++ b/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/PropertiesProviderAdapterImpl.java @@ -0,0 +1,37 @@ +/** + * @author Tomas Olvecky + * + * 11 2013 + * + * Copyright (c) 2013 by Cisco Systems, Inc. + * All rights reserved. + */ +package org.opendaylight.controller.netconf.persist.impl; + +import org.opendaylight.controller.config.persist.api.PropertiesProvider; +import org.opendaylight.controller.netconf.persist.impl.osgi.PropertiesProviderBaseImpl; + +public class PropertiesProviderAdapterImpl implements PropertiesProvider { + private final PropertiesProviderBaseImpl inner; + private final String index; + + public PropertiesProviderAdapterImpl(PropertiesProviderBaseImpl inner, String index) { + this.inner = inner; + this.index = index; + } + + @Override + public String getProperty(String key) { + String fullKey = getFullKeyForReporting(key); + return inner.getPropertyWithoutPrefix(fullKey); + } + + public String getPrefix() { + return inner.getPrefix() + "." + index + ".properties"; + } + + @Override + public String getFullKeyForReporting(String key) { + return getPrefix() + "." + key; + } +} 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 index ae6c95312c..5fa0b49d7b 100644 --- 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 @@ -8,12 +8,9 @@ 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.netconf.persist.impl.PersisterAggregator; 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; @@ -22,43 +19,45 @@ import org.slf4j.LoggerFactory; import javax.management.MBeanServer; import java.lang.management.ManagementFactory; import java.net.InetSocketAddress; +import java.util.regex.Pattern; public class ConfigPersisterActivator implements BundleActivator { private static final Logger logger = LoggerFactory.getLogger(ConfigPersisterActivator.class); private final static MBeanServer platformMBeanServer = ManagementFactory.getPlatformMBeanServer(); + private static final String IGNORED_MISSING_CAPABILITY_REGEX_SUFFIX = "ignoredMissingCapabilityRegex"; private ConfigPersisterNotificationHandler configPersisterNotificationHandler; private Thread initializationThread; - @Override - public void start(BundleContext context) throws Exception { - logger.debug("ConfigPersister activator started"); + public static final String NETCONF_CONFIG_PERSISTER = "netconf.config.persister"; + public static final String STORAGE_ADAPTER_CLASS_PROP_SUFFIX = "storageAdapterClass"; + public static final String DEFAULT_IGNORED_REGEX = "^urn:ietf:params:xml:ns:netconf:base:1.0"; - Optional maybePersister = PersisterImpl.createFromProperties(context); - 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"); - } + @Override + public void start(final BundleContext context) throws Exception { + logger.debug("ConfigPersister starting"); - Optional maybeTLSConfiguration = NetconfConfigUtil.extractTLSConfiguration(context); - Optional maybeTCPAddress = NetconfConfigUtil.extractTCPNetconfAddress(context); + PropertiesProviderBaseImpl propertiesProvider = new PropertiesProviderBaseImpl(context); - InetSocketAddress address; - if (maybeTLSConfiguration.isPresent()) { - throw new UnsupportedOperationException("TLS is currently not supported for persister"); - } else if (maybeTCPAddress.isPresent()) { - address = maybeTCPAddress.get(); + String regexProperty = propertiesProvider.getProperty(IGNORED_MISSING_CAPABILITY_REGEX_SUFFIX); + String regex; + if (regexProperty != null) { + regex = regexProperty; } else { - throw new IllegalStateException("Netconf is not configured, persister is not operational"); + regex = DEFAULT_IGNORED_REGEX; } + Pattern ignoredMissingCapabilityRegex = Pattern.compile(regex); + PersisterAggregator persister = PersisterAggregator.createFromProperties(propertiesProvider); - PersisterImpl persister = maybePersister.get(); + InetSocketAddress address = NetconfConfigUtil.extractTCPNetconfAddress(context, + "Netconf is not configured, persister is not operational"); configPersisterNotificationHandler = new ConfigPersisterNotificationHandler(persister, address, - platformMBeanServer); + platformMBeanServer, ignoredMissingCapabilityRegex); + + // offload initialization to another thread in order to stop blocking activator Runnable initializationRunnable = new Runnable() { @Override public void run() { diff --git a/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/osgi/PropertiesProviderBaseImpl.java b/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/osgi/PropertiesProviderBaseImpl.java new file mode 100644 index 0000000000..15ed5c48fa --- /dev/null +++ b/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/osgi/PropertiesProviderBaseImpl.java @@ -0,0 +1,44 @@ +/** + * @author Tomas Olvecky + * + * 11 2013 + * + * Copyright (c) 2013 by Cisco Systems, Inc. + * All rights reserved. + */ +package org.opendaylight.controller.netconf.persist.impl.osgi; + +import org.opendaylight.controller.config.persist.api.PropertiesProvider; +import org.osgi.framework.BundleContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PropertiesProviderBaseImpl implements PropertiesProvider { + + private static final Logger logger = LoggerFactory.getLogger(PropertiesProviderBaseImpl.class); + private final BundleContext bundleContext; + + public PropertiesProviderBaseImpl(BundleContext bundleContext) { + this.bundleContext = bundleContext; + } + + @Override + public String getProperty(String key) { + String fullKey = getFullKeyForReporting(key); + return getPropertyWithoutPrefix(fullKey); + } + + public String getPropertyWithoutPrefix(String fullKey){ + logger.trace("Full key {}", fullKey); + return bundleContext.getProperty(fullKey); + } + + public String getPrefix(){ + return ConfigPersisterActivator.NETCONF_CONFIG_PERSISTER; + } + + @Override + public String getFullKeyForReporting(String key) { + return getPrefix() + "." + key; + } +} diff --git a/opendaylight/netconf/config-persister-impl/src/test/java/org/opendaylight/controller/netconf/persist/impl/CapabilityStrippingConfigSnapshotHolderTest.java b/opendaylight/netconf/config-persister-impl/src/test/java/org/opendaylight/controller/netconf/persist/impl/CapabilityStrippingConfigSnapshotHolderTest.java new file mode 100644 index 0000000000..d91712f347 --- /dev/null +++ b/opendaylight/netconf/config-persister-impl/src/test/java/org/opendaylight/controller/netconf/persist/impl/CapabilityStrippingConfigSnapshotHolderTest.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.persist.impl; + +import com.google.common.collect.Sets; +import org.apache.commons.io.IOUtils; +import org.junit.Test; +import org.opendaylight.controller.netconf.persist.impl.osgi.ConfigPersisterActivator; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.w3c.dom.Element; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Pattern; + +import static org.junit.Assert.assertEquals; + +public class CapabilityStrippingConfigSnapshotHolderTest { + + @Test + public void testCapabilityStripping() throws Exception { + Set allCapabilities = readLines("/capabilities-all.txt"); + Set expectedCapabilities = readLines("/capabilities-stripped.txt"); + String snapshotAsString = readToString("/snapshot.xml"); + Element element = XmlUtil.readXmlToElement(snapshotAsString); + { + CapabilityStrippingConfigSnapshotHolder tested = new CapabilityStrippingConfigSnapshotHolder( + element, allCapabilities, Pattern.compile( + ConfigPersisterActivator.DEFAULT_IGNORED_REGEX + )); + assertEquals(expectedCapabilities, tested.getCapabilities()); + assertEquals(Collections.emptySet(), tested.getMissingNamespaces()); + } + { + // test regex + CapabilityStrippingConfigSnapshotHolder tested = new CapabilityStrippingConfigSnapshotHolder( + element, allCapabilities, Pattern.compile( + "^bar" + )); + assertEquals(expectedCapabilities, tested.getCapabilities()); + assertEquals(Sets.newHashSet(ConfigPersisterActivator.DEFAULT_IGNORED_REGEX.substring(1)), + tested.getMissingNamespaces()); + } + } + + private Set readLines(String fileName) throws IOException { + return new HashSet<>(IOUtils.readLines(getClass().getResourceAsStream(fileName))); + } + + private String readToString(String fileName) throws IOException { + return IOUtils.toString(getClass().getResourceAsStream(fileName)); + } + +} diff --git a/opendaylight/netconf/config-persister-impl/src/test/java/org/opendaylight/controller/netconf/persist/impl/PersisterAggregatorTest.java b/opendaylight/netconf/config-persister-impl/src/test/java/org/opendaylight/controller/netconf/persist/impl/PersisterAggregatorTest.java new file mode 100644 index 0000000000..1b4031ac57 --- /dev/null +++ b/opendaylight/netconf/config-persister-impl/src/test/java/org/opendaylight/controller/netconf/persist/impl/PersisterAggregatorTest.java @@ -0,0 +1,194 @@ +/* + * 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.Ignore; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder; +import org.opendaylight.controller.config.persist.api.Persister; +import org.opendaylight.controller.config.persist.api.PropertiesProvider; +import org.opendaylight.controller.config.persist.api.StorageAdapter; +import org.opendaylight.controller.config.persist.storage.file.FileStorageAdapter; +import org.opendaylight.controller.netconf.persist.impl.osgi.ConfigPersisterActivator; +import org.opendaylight.controller.netconf.persist.impl.osgi.PropertiesProviderBaseImpl; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.junit.matchers.JUnitMatchers.containsString; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +public class PersisterAggregatorTest { + @Mock + TestingPropertiesProvider propertiesProvider; + + class TestingPropertiesProvider extends PropertiesProviderBaseImpl { + TestingPropertiesProvider() { + super(null); + } + + @Override + public String getFullKeyForReporting(String key) { + return "prefix." + key; + } + + @Override + public String getProperty(String key) { + throw new UnsupportedOperationException("should be mocked"); + } + } + + @Before + public void setUpMocks() { + MockitoAnnotations.initMocks(this); + doCallRealMethod().when(propertiesProvider).getFullKeyForReporting(anyString()); + } + + @Ignore + @Test + public void testFromProperties() throws Exception { + doReturn("").when(propertiesProvider).getProperty(ConfigPersisterActivator.NETCONF_CONFIG_PERSISTER); + doReturn(MockAdapter.class.getName()).when(propertiesProvider).getProperty( + ConfigPersisterActivator.STORAGE_ADAPTER_CLASS_PROP_SUFFIX); + doReturn("false").when(propertiesProvider).getProperty("readOnly"); + + PersisterAggregator persisterAggregator = PersisterAggregator.createFromProperties(propertiesProvider); + persisterAggregator.persistConfig(null); + persisterAggregator.loadLastConfig(); + persisterAggregator.persistConfig(null); + persisterAggregator.loadLastConfig(); + + assertEquals(2, MockAdapter.persist); + assertEquals(2, MockAdapter.load); + assertEquals(1, MockAdapter.props); + } + + + @Ignore + @Test + public void testFromProperties2() throws Exception { + String prefix = ""; + doReturn(prefix).when(propertiesProvider).getProperty(ConfigPersisterActivator.NETCONF_CONFIG_PERSISTER); + doReturn(FileStorageAdapter.class.getName()).when(propertiesProvider).getProperty( + ConfigPersisterActivator.STORAGE_ADAPTER_CLASS_PROP_SUFFIX); + + doReturn("target" + File.separator + "generated-test-sources" + File.separator + "testFile").when( + propertiesProvider).getProperty("prefix.properties.fileStorage"); + doReturn("propertiesProvider").when(propertiesProvider).toString(); + doReturn(null).when(propertiesProvider).getProperty("prefix.properties.numberOfBackups"); + + PersisterAggregator persisterAggregator = PersisterAggregator.createFromProperties(propertiesProvider); + } + + @Ignore + @Test + public void testFromProperties3() throws Exception { + doReturn("").when(propertiesProvider).getProperty(ConfigPersisterActivator.NETCONF_CONFIG_PERSISTER); + doReturn(FileStorageAdapter.class.getName()).when(propertiesProvider).getProperty( + ConfigPersisterActivator.STORAGE_ADAPTER_CLASS_PROP_SUFFIX); + doReturn("target" + File.separator + "generated-test-sources" + File.separator + "testFile").when( + propertiesProvider).getProperty("prefix.properties.fileStorage"); + doReturn("false").when(propertiesProvider).getProperty("readOnly"); + doReturn("propertiesProvider").when(propertiesProvider).toString(); + doReturn("0").when(propertiesProvider).getProperty("prefix.properties.numberOfBackups"); + try { + PersisterAggregator.createFromProperties(propertiesProvider); + 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 loadLastConfig() throws Exception { + List persisterWithConfigurations = new ArrayList<>(); + PersisterAggregator.PersisterWithConfiguration first = new PersisterAggregator.PersisterWithConfiguration(mock(Persister.class), false); + + ConfigSnapshotHolder ignored = mock(ConfigSnapshotHolder.class); + doReturn(Optional.of(ignored)).when(first.storage).loadLastConfig(); // should be ignored + + ConfigSnapshotHolder used = mock(ConfigSnapshotHolder.class); + PersisterAggregator.PersisterWithConfiguration second = new PersisterAggregator.PersisterWithConfiguration(mock(Persister.class), false); + doReturn(Optional.of(used)).when(second.storage).loadLastConfig(); // should be used + + PersisterAggregator.PersisterWithConfiguration third = new PersisterAggregator.PersisterWithConfiguration(mock(Persister.class), false); + doReturn(Optional.absent()).when(third.storage).loadLastConfig(); + + persisterWithConfigurations.add(first); + persisterWithConfigurations.add(second); + persisterWithConfigurations.add(third); + + PersisterAggregator persisterAggregator = new PersisterAggregator(persisterWithConfigurations); + Optional configSnapshotHolderOptional = persisterAggregator.loadLastConfig(); + assertTrue(configSnapshotHolderOptional.isPresent()); + assertEquals(used, configSnapshotHolderOptional.get()); + } + + @Ignore + @Test + public void test() throws Exception { +// Persister storage = mock(Persister.class); +// doReturn(null).when(storage).loadLastConfig(); +// doNothing().when(storage).persistConfig(any(ConfigSnapshotHolder.class)); +// +// PersisterAggregator persister = new PersisterAggregator(storage); +// persister.loadLastConfig(); +// persister.persistConfig(null); +// +// verify(storage).loadLastConfig(); +// verify(storage).persistConfig(any(ConfigSnapshotHolder.class)); + } + + public static class MockAdapter implements StorageAdapter, Persister { + + 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 Optional.absent(); + } + + static int props = 0; + + @Override + public Persister instantiate(PropertiesProvider propertiesProvider) { + props++; + return this; + } + + @Override + public void close() { + } + + } + +} 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 deleted file mode 100644 index 44b3b61043..0000000000 --- a/opendaylight/netconf/config-persister-impl/src/test/java/org/opendaylight/controller/netconf/persist/impl/PersisterImplTest.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.controller.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.osgi.framework.BundleContext; - -import java.io.File; -import java.io.IOException; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.junit.matchers.JUnitMatchers.containsString; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -public class PersisterImplTest { - @Mock - BundleContext mockedContext; - - @Before - public void setUpMocks() { - MockitoAnnotations.initMocks(this); - } - - @Test - public void testFromProperties() throws Exception { - doReturn(MockAdapter.class.getName()).when(mockedContext).getProperty( - PersisterImpl.STORAGE_ADAPTER_CLASS_PROP); - - PersisterImpl persisterImpl = PersisterImpl.createFromProperties(mockedContext).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 { - mockedContext = mock(BundleContext.class); - doReturn(FileStorageAdapter.class.getName()).when(mockedContext).getProperty( - PersisterImpl.STORAGE_ADAPTER_CLASS_PROP); - doReturn("target" + File.separator + "generated-test-sources" + File.separator + "testFile").when( - mockedContext).getProperty(FileStorageAdapter.FILE_STORAGE_PROP); - doReturn("mockedContext").when(mockedContext).toString(); - doReturn(null).when(mockedContext).getProperty("numberOfBackups"); - - PersisterImpl persisterImpl = PersisterImpl.createFromProperties(mockedContext).get(); - assertTrue(persisterImpl.getStorage() instanceof FileStorageAdapter); - } - - @Test - public void testFromProperties3() throws Exception { - mockedContext = mock(BundleContext.class); - doReturn(FileStorageAdapter.class.getName()).when(mockedContext).getProperty( - PersisterImpl.STORAGE_ADAPTER_CLASS_PROP); - doReturn("target" + File.separator + "generated-test-sources" + File.separator + "testFile").when( - mockedContext).getProperty(FileStorageAdapter.FILE_STORAGE_PROP); - doReturn("mockedContext").when(mockedContext).toString(); - doReturn("0").when(mockedContext).getProperty("numberOfBackups"); - try { - PersisterImpl.createFromProperties(mockedContext).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(BundleContext configProvider) { - props++; - } - - @Override - public void close() throws IOException { - // TODO Auto-generated method stub - - } - - } - -} diff --git a/opendaylight/netconf/config-persister-impl/src/test/resources/capabilities-all.txt b/opendaylight/netconf/config-persister-impl/src/test/resources/capabilities-all.txt new file mode 100644 index 0000000000..84c85b740c --- /dev/null +++ b/opendaylight/netconf/config-persister-impl/src/test/resources/capabilities-all.txt @@ -0,0 +1,20 @@ +urn:opendaylight:l2:types?module=opendaylight-l2-types&revision=2013-08-27 +urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding?module=opendaylight-md-sal-binding&revision=2013-10-28 +urn:opendaylight:params:xml:ns:yang:controller:threadpool?module=threadpool&revision=2013-04-09 +urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom?module=opendaylight-md-sal-dom&revision=2013-10-28 +urn:opendaylight:params:xml:ns:yang:controller:config?module=config&revision=2013-04-05 +urn:ietf:params:netconf:capability:candidate:1.0 +urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring?module=ietf-netconf-monitoring&revision=2010-10-04 +urn:opendaylight:params:xml:ns:yang:controller:netty:eventexecutor?module=netty-event-executor&revision=2013-11-12 +urn:ietf:params:xml:ns:yang:rpc-context?module=rpc-context&revision=2013-06-17 +urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding:impl?module=opendaylight-sal-binding-broker-impl&revision=2013-10-28 +urn:ietf:params:xml:ns:yang:ietf-inet-types?module=ietf-inet-types&revision=2010-09-24 +urn:ietf:params:netconf:capability:rollback-on-error:1.0 +urn:ietf:params:xml:ns:yang:ietf-yang-types?module=ietf-yang-types&revision=2010-09-24 +urn:opendaylight:params:xml:ns:yang:controller:threadpool:impl?module=threadpool-impl&revision=2013-04-05 +urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:impl?module=opendaylight-sal-dom-broker-impl&revision=2013-10-28 +urn:opendaylight:params:xml:ns:yang:controller:logback:config?module=config-logging&revision=2013-07-16 +urn:opendaylight:yang:extension:yang-ext?module=yang-ext&revision=2013-07-09 +urn:opendaylight:params:xml:ns:yang:iana?module=iana&revision=2013-08-16 +urn:opendaylight:params:xml:ns:yang:controller:md:sal:common?module=opendaylight-md-sal-common&revision=2013-10-28 +urn:opendaylight:params:xml:ns:yang:ieee754?module=ieee754&revision=2013-08-19 diff --git a/opendaylight/netconf/config-persister-impl/src/test/resources/capabilities-stripped.txt b/opendaylight/netconf/config-persister-impl/src/test/resources/capabilities-stripped.txt new file mode 100644 index 0000000000..4a0b7ffffd --- /dev/null +++ b/opendaylight/netconf/config-persister-impl/src/test/resources/capabilities-stripped.txt @@ -0,0 +1,5 @@ +urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding?module=opendaylight-md-sal-binding&revision=2013-10-28 +urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom?module=opendaylight-md-sal-dom&revision=2013-10-28 +urn:opendaylight:params:xml:ns:yang:controller:config?module=config&revision=2013-04-05 +urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding:impl?module=opendaylight-sal-binding-broker-impl&revision=2013-10-28 +urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:impl?module=opendaylight-sal-dom-broker-impl&revision=2013-10-28 diff --git a/opendaylight/netconf/config-persister-impl/src/test/resources/logback-test.xml b/opendaylight/netconf/config-persister-impl/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..a4ff3abe49 --- /dev/null +++ b/opendaylight/netconf/config-persister-impl/src/test/resources/logback-test.xml @@ -0,0 +1,13 @@ + + + + + %date{"yyyy-MM-dd HH:mm:ss.SSS z"} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + diff --git a/opendaylight/netconf/config-persister-impl/src/test/resources/snapshot.xml b/opendaylight/netconf/config-persister-impl/src/test/resources/snapshot.xml new file mode 100644 index 0000000000..a6a57d704a --- /dev/null +++ b/opendaylight/netconf/config-persister-impl/src/test/resources/snapshot.xml @@ -0,0 +1,103 @@ + + + + prefix:schema-service-singleton + yang-schema-service + + + prefix:hash-map-data-store + hash-map-data-store + + + prefix:dom-broker-impl + dom-broker + + dom:dom-data-store + ref_hash-map-data-store + + + + prefix:binding-broker-impl + binding-broker-impl + + binding:binding-notification-service + ref_binding-notification-broker + + + binding:binding-data-broker + ref_binding-data-broker + + + + prefix:runtime-generated-mapping + runtime-mapping-singleton + + + prefix:binding-notification-broker + binding-notification-broker + + + prefix:binding-data-broker + binding-data-broker + + dom:dom-broker-osgi-registry + ref_dom-broker + + + binding:binding-dom-mapping-service + ref_runtime-mapping-singleton + + + + + + dom:schema-service + + ref_yang-schema-service + /config/modules/module[name='schema-service-singleton']/instance[name='yang-schema-service'] + + + + binding:binding-notification-service + + ref_binding-notification-broker + /config/modules/module[name='binding-notification-broker']/instance[name='binding-notification-broker'] + + + + dom:dom-data-store + + ref_hash-map-data-store + /config/modules/module[name='hash-map-data-store']/instance[name='hash-map-data-store'] + + + + binding:binding-broker-osgi-registry + + ref_binding-broker-impl + /config/modules/module[name='binding-broker-impl']/instance[name='binding-broker-impl'] + + + + binding-impl:binding-dom-mapping-service + + ref_runtime-mapping-singleton + /config/modules/module[name='runtime-generated-mapping']/instance[name='runtime-mapping-singleton'] + + + + dom:dom-broker-osgi-registry + + ref_dom-broker + /config/modules/module[name='dom-broker-impl']/instance[name='dom-broker'] + + + + binding:binding-data-broker + + ref_binding-data-broker + /config/modules/module[name='binding-data-broker']/instance[name='binding-data-broker'] + + + + diff --git a/opendaylight/netconf/netconf-client/pom.xml b/opendaylight/netconf/netconf-client/pom.xml index ffd46e882c..de4fb101f9 100644 --- a/opendaylight/netconf/netconf-client/pom.xml +++ b/opendaylight/netconf/netconf-client/pom.xml @@ -60,11 +60,12 @@ javax.xml.xpath, org.opendaylight.controller.netconf.api, org.opendaylight.controller.netconf.util, - org.opendaylight.controller.netconf.util.xml, + org.opendaylight.controller.netconf.util.*, org.opendaylight.protocol.framework, org.slf4j, org.w3c.dom, - org.xml.sax + org.xml.sax, + io.netty.handler.codec diff --git a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfSshClientDispatcher.java b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfSshClientDispatcher.java index ce0f427475..b19c09263b 100644 --- a/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfSshClientDispatcher.java +++ b/opendaylight/netconf/netconf-client/src/main/java/org/opendaylight/controller/netconf/client/NetconfSshClientDispatcher.java @@ -8,11 +8,123 @@ package org.opendaylight.controller.netconf.client; +import java.io.IOException; +import java.net.InetSocketAddress; + +import javax.net.ssl.SSLContext; + +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.controller.netconf.util.handler.FramingMechanismHandlerFactory; +import org.opendaylight.controller.netconf.util.handler.NetconfMessageAggregator; +import org.opendaylight.controller.netconf.util.handler.ssh.SshHandler; +import org.opendaylight.controller.netconf.util.handler.ssh.authentication.AuthenticationHandler; +import org.opendaylight.controller.netconf.util.handler.ssh.client.Invoker; +import org.opendaylight.controller.netconf.util.messages.FramingMechanism; +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 org.opendaylight.protocol.framework.ReconnectStrategy; +import org.opendaylight.protocol.framework.SessionListener; +import org.opendaylight.protocol.framework.SessionListenerFactory; + +import com.google.common.base.Optional; + +import io.netty.channel.ChannelHandler; import io.netty.channel.EventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.util.HashedWheelTimer; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.Promise; public class NetconfSshClientDispatcher extends NetconfClientDispatcher { - public NetconfSshClientDispatcher(EventLoopGroup bossGroup, EventLoopGroup workerGroup) { - super(null, bossGroup, workerGroup); + private AuthenticationHandler authHandler; + private HashedWheelTimer timer; + private NetconfClientSessionNegotiatorFactory negotatorFactory; + + public NetconfSshClientDispatcher(AuthenticationHandler authHandler, EventLoopGroup bossGroup, + EventLoopGroup workerGroup) { + super(Optional. absent(), bossGroup, workerGroup); + this.authHandler = authHandler; + this.timer = new HashedWheelTimer(); + this.negotatorFactory = new NetconfClientSessionNegotiatorFactory(timer); + } + + @Override + public Future createClient(InetSocketAddress address, + final NetconfClientSessionListener sessionListener, ReconnectStrategy strat) { + return super.createClient(address, strat, new PipelineInitializer() { + + @Override + public void initializeChannel(SocketChannel arg0, Promise arg1) { + new NetconfSshClientInitializer(authHandler, negotatorFactory, sessionListener).initialize(arg0, arg1); + } + + }); + } + + private static final class NetconfSshClientInitializer extends AbstractChannelInitializer { + + private final NetconfHandlerFactory handlerFactory; + private final AuthenticationHandler authenticationHandler; + private final NetconfClientSessionNegotiatorFactory negotiatorFactory; + private final NetconfClientSessionListener sessionListener; + + public NetconfSshClientInitializer(AuthenticationHandler authHandler, + NetconfClientSessionNegotiatorFactory negotiatorFactory, + final NetconfClientSessionListener sessionListener) { + this.handlerFactory = new NetconfHandlerFactory(new NetconfMessageFactory()); + this.authenticationHandler = authHandler; + this.negotiatorFactory = negotiatorFactory; + this.sessionListener = sessionListener; + } + + @Override + public void initialize(SocketChannel ch, Promise promise) { + try { + Invoker invoker = Invoker.subsystem("netconf"); + ch.pipeline().addFirst(new SshHandler(authenticationHandler, invoker)); + ch.pipeline().addLast("aggregator", new NetconfMessageAggregator(FramingMechanism.EOM)); + ch.pipeline().addLast(handlerFactory.getDecoders()); + initializeAfterDecoder(ch, promise); + ch.pipeline().addLast("frameEncoder", + FramingMechanismHandlerFactory.createHandler(FramingMechanism.EOM)); + ch.pipeline().addLast(handlerFactory.getEncoders()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + protected void initializeAfterDecoder(SocketChannel ch, Promise promise) { + ch.pipeline().addLast("negotiator", negotiatorFactory.getSessionNegotiator(new SessionListenerFactory() { + @Override + public SessionListener getSessionListener() { + return sessionListener; + } + }, ch, promise)); + + } + } + + 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-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 index 890bbe7288..b334c354fb 100644 --- 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 @@ -16,7 +16,6 @@ import org.opendaylight.controller.netconf.impl.NetconfServerSessionListenerFact import org.opendaylight.controller.netconf.impl.NetconfServerSessionNegotiatorFactory; import org.opendaylight.controller.netconf.impl.SessionIdProvider; 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; @@ -30,9 +29,6 @@ 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; @@ -41,11 +37,8 @@ public class NetconfImplActivator implements BundleActivator { @Override public void start(final BundleContext context) throws Exception { - maybeTCPAddress = NetconfConfigUtil.extractTCPNetconfAddress(context); - maybeTLSConfiguration = NetconfConfigUtil.extractTLSConfiguration(context); - if (maybeTCPAddress.isPresent() == false && maybeTLSConfiguration.isPresent() == false) { - throw new IllegalStateException("TCP nor TLS is configured, netconf not available."); - } + InetSocketAddress address = NetconfConfigUtil.extractTCPNetconfAddress(context, "TCP is not configured, netconf not available."); + NetconfOperationServiceFactoryListenerImpl factoriesListener = new NetconfOperationServiceFactoryListenerImpl(); factoriesTracker = new NetconfOperationServiceFactoryTracker(context, factoriesListener); factoriesTracker.open(); @@ -62,26 +55,13 @@ public class NetconfImplActivator implements BundleActivator { eventLoopGroup = new NioEventLoopGroup(); - if (maybeTCPAddress.isPresent()) { - Optional maybeSSLContext = Optional.absent(); - InetSocketAddress address = maybeTCPAddress.get(); - NetconfServerDispatcher.ServerSslChannelInitializer serverChannelInitializer = new NetconfServerDispatcher.ServerSslChannelInitializer( - maybeSSLContext, serverNegotiatorFactory, listenerFactory); - dispatch = new NetconfServerDispatcher(serverChannelInitializer, eventLoopGroup, eventLoopGroup); - - 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(); - NetconfServerDispatcher.ServerSslChannelInitializer serverChannelInitializer = new NetconfServerDispatcher.ServerSslChannelInitializer( - maybeSSLContext, serverNegotiatorFactory, listenerFactory); - dispatch = new NetconfServerDispatcher(serverChannelInitializer, eventLoopGroup, eventLoopGroup); - - logger.info("Starting TLS netconf server at {}", address); - dispatch.createServer(address); - } + NetconfServerDispatcher.ServerSslChannelInitializer serverChannelInitializer = new NetconfServerDispatcher.ServerSslChannelInitializer( + Optional.absent(), serverNegotiatorFactory, listenerFactory); + dispatch = new NetconfServerDispatcher(serverChannelInitializer, eventLoopGroup, eventLoopGroup); + + logger.info("Starting TCP netconf server at {}", address); + dispatch.createServer(address); + } @Override 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 index e5b9fa3ffc..6c244a0b97 100644 --- 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 @@ -27,6 +27,7 @@ import java.util.Collection; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; import javax.management.ObjectName; import javax.net.ssl.SSLContext; import javax.xml.parsers.ParserConfigurationException; @@ -65,6 +66,7 @@ import org.opendaylight.controller.netconf.impl.mapping.ExiDecoderHandler; import org.opendaylight.controller.netconf.impl.mapping.ExiEncoderHandler; import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationServiceFactoryListenerImpl; import org.opendaylight.controller.netconf.persist.impl.ConfigPersisterNotificationHandler; +import org.opendaylight.controller.netconf.persist.impl.osgi.ConfigPersisterActivator; import org.opendaylight.controller.netconf.ssh.NetconfSSHServer; import org.opendaylight.controller.netconf.util.test.XmlFileLoader; import org.opendaylight.controller.netconf.util.xml.ExiParameters; @@ -204,8 +206,8 @@ public class NetconfITTest extends AbstractConfigTest { @Test public void testTwoSessions() throws Exception { - try (NetconfClient netconfClient = new NetconfClient("1", tcpAddress, 4000, clientDispatcher)) { - try (NetconfClient netconfClient2 = new NetconfClient("2", tcpAddress, 4000, clientDispatcher)) { + try (NetconfClient netconfClient = new NetconfClient("1", tcpAddress, 10000, clientDispatcher)) { + try (NetconfClient netconfClient2 = new NetconfClient("2", tcpAddress, 10000, clientDispatcher)) { } } } @@ -215,7 +217,8 @@ public class NetconfITTest extends AbstractConfigTest { Persister persister = mock(Persister.class); doReturn("mockPersister").when(persister).toString(); doReturn(Optional.absent()).when(persister).loadLastConfig(); - ConfigPersisterNotificationHandler h = new ConfigPersisterNotificationHandler(persister, tcpAddress, ManagementFactory.getPlatformMBeanServer()); + ConfigPersisterNotificationHandler h = + new ConfigPersisterNotificationHandler(persister, tcpAddress, ManagementFactory.getPlatformMBeanServer(), Pattern.compile(ConfigPersisterActivator.DEFAULT_IGNORED_REGEX)); h.init(); } diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/osgi/NetconfSSHActivator.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/osgi/NetconfSSHActivator.java index 6626f47b03..d2f6c8c81c 100644 --- a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/osgi/NetconfSSHActivator.java +++ b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/osgi/NetconfSSHActivator.java @@ -37,10 +37,11 @@ public class NetconfSSHActivator implements BundleActivator{ logger.trace("Starting netconf SSH bridge."); Optional sshSocketAddressOptional = NetconfConfigUtil.extractSSHNetconfAddress(context); - Optional tcpSocketAddressOptional = NetconfConfigUtil.extractTCPNetconfAddress(context); + InetSocketAddress tcpSocketAddress = NetconfConfigUtil.extractTCPNetconfAddress(context, + "TCP is not configured, netconf ssh bridge is not available."); - if (sshSocketAddressOptional.isPresent() && tcpSocketAddressOptional.isPresent()){ - server = NetconfSSHServer.start(sshSocketAddressOptional.get().getPort(),tcpSocketAddressOptional.get()); + if (sshSocketAddressOptional.isPresent()){ + server = NetconfSSHServer.start(sshSocketAddressOptional.get().getPort(),tcpSocketAddress); Thread serverThread = new Thread(server,"netconf SSH server thread"); serverThread.setDaemon(true); serverThread.start(); diff --git a/opendaylight/netconf/netconf-util/pom.xml b/opendaylight/netconf/netconf-util/pom.xml index c19506b236..353dd1aae7 100644 --- a/opendaylight/netconf/netconf-util/pom.xml +++ b/opendaylight/netconf/netconf-util/pom.xml @@ -76,6 +76,7 @@ org.opendaylight.controller.netconf.util.mapping, org.opendaylight.controller.netconf.util.messages, org.opendaylight.controller.netconf.util.handler, + org.opendaylight.controller.netconf.util.handler.*, com.google.common.base, diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/SshHandler.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/SshHandler.java index b911989c64..0d9096c02a 100644 --- a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/SshHandler.java +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/SshHandler.java @@ -8,13 +8,16 @@ package org.opendaylight.controller.netconf.util.handler.ssh; +import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelOutboundHandlerAdapter; import io.netty.channel.ChannelPromise; + import java.io.IOException; import java.net.SocketAddress; + import org.opendaylight.controller.netconf.util.handler.ssh.authentication.AuthenticationHandler; import org.opendaylight.controller.netconf.util.handler.ssh.client.Invoker; import org.opendaylight.controller.netconf.util.handler.ssh.client.SshClient; @@ -50,7 +53,7 @@ public class SshHandler extends ChannelOutboundHandlerAdapter { @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { - this.sshClientAdapter.write((String) msg); + this.sshClientAdapter.write((ByteBuf) msg); } @Override diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/authentication/LoginPassword.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/authentication/LoginPassword.java index bb0d37899d..2f1b260bd0 100644 --- a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/authentication/LoginPassword.java +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/authentication/LoginPassword.java @@ -13,7 +13,8 @@ import ch.ethz.ssh2.Connection; import java.io.IOException; /** - * Class Providing username/password authentication option to {@link org.opendaylight.controller.netconf.util.handler.ssh.SshHandler} + * Class Providing username/password authentication option to + * {@link org.opendaylight.controller.netconf.util.handler.ssh.SshHandler} */ public class LoginPassword extends AuthenticationHandler { private final String username; @@ -28,6 +29,7 @@ public class LoginPassword extends AuthenticationHandler { public void authenticate(Connection connection) throws IOException { boolean isAuthenticated = connection.authenticateWithPassword(username, password); - if (isAuthenticated == false) throw new IOException("Authentication failed."); + if (isAuthenticated == false) + throw new IOException("Authentication failed."); } } diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClient.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClient.java index c43aa6f3e5..3cb608db6a 100644 --- a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClient.java +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClient.java @@ -18,7 +18,6 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; - /** * Wrapper class around GANYMED SSH java library. */ @@ -28,16 +27,16 @@ public class SshClient { private final AuthenticationHandler authenticationHandler; private Connection connection; - public SshClient(VirtualSocket socket, - AuthenticationHandler authenticationHandler) throws IOException { + public SshClient(VirtualSocket socket, AuthenticationHandler authenticationHandler) throws IOException { this.socket = socket; this.authenticationHandler = authenticationHandler; } public SshSession openSession() throws IOException { - if(connection == null) connect(); + if (connection == null) + connect(); - Session session = connection.openSession(); + Session session = connection.openSession(); SshSession sshSession = new SshSession(session); openSessions.put(openSessions.size(), sshSession); @@ -46,22 +45,24 @@ public class SshClient { private void connect() throws IOException { connection = new Connection(socket); + connection.connect(); authenticationHandler.authenticate(connection); } public void closeSession(SshSession session) { - if( session.getState() == Channel.STATE_OPEN - || session.getState() == Channel.STATE_OPENING) { + if (session.getState() == Channel.STATE_OPEN || session.getState() == Channel.STATE_OPENING) { session.session.close(); } } public void close() { - for(SshSession session : openSessions.values()) closeSession(session); + for (SshSession session : openSessions.values()) + closeSession(session); openSessions.clear(); - if(connection != null) connection.close(); + if (connection != null) + connection.close(); } } diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClientAdapter.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClientAdapter.java index a50462e40d..4213fe3e06 100644 --- a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClientAdapter.java +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/handler/ssh/client/SshClientAdapter.java @@ -12,14 +12,19 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.LinkedList; +import java.util.Queue; import java.util.concurrent.atomic.AtomicBoolean; import org.opendaylight.controller.netconf.util.handler.ssh.virtualsocket.VirtualSocketException; + /** - * Worker thread class. Handles all downstream and upstream events in SSH Netty pipeline. + * Worker thread class. Handles all downstream and upstream events in SSH Netty + * pipeline. */ public class SshClientAdapter implements Runnable { private final SshClient sshClient; @@ -30,6 +35,9 @@ public class SshClientAdapter implements Runnable { private InputStream stdErr; private OutputStream stdIn; + private Queue postponned = new LinkedList<>(); + + private ChannelHandlerContext ctx; private ChannelPromise disconnectPromise; @@ -37,8 +45,7 @@ public class SshClientAdapter implements Runnable { private final Object lock = new Object(); - public SshClientAdapter(SshClient sshClient, - Invoker invoker) { + public SshClientAdapter(SshClient sshClient, Invoker invoker) { this.sshClient = sshClient; this.invoker = invoker; } @@ -47,18 +54,24 @@ public class SshClientAdapter implements Runnable { try { session = sshClient.openSession(); invoker.invoke(session); - stdOut = session.getStdout(); stdErr = session.getStderr(); - synchronized(lock) { + synchronized (lock) { + stdIn = session.getStdin(); + ByteBuf message = null; + while ((message = postponned.poll()) != null) { + writeImpl(message); + } } while (stopRequested.get() == false) { byte[] readBuff = new byte[1024]; int c = stdOut.read(readBuff); - + if (c == -1) { + continue; + } byte[] tranBuff = new byte[c]; System.arraycopy(readBuff, 0, tranBuff, 0, c); @@ -76,17 +89,25 @@ public class SshClientAdapter implements Runnable { sshClient.close(); synchronized (lock) { - if(disconnectPromise != null) ctx.disconnect(disconnectPromise); + if (disconnectPromise != null) + ctx.disconnect(disconnectPromise); } } } // TODO: needs rework to match netconf framer API. - public void write(String message) throws IOException { + public void write(ByteBuf message) throws IOException { synchronized (lock) { - if (stdIn == null) throw new IllegalStateException("StdIn not available"); + if (stdIn == null) { + postponned.add(message); + return; + } + writeImpl(message); } - stdIn.write(message.getBytes()); + } + + private void writeImpl(ByteBuf message) throws IOException { + message.getBytes(0, stdIn, message.readableBytes()); stdIn.flush(); } @@ -98,8 +119,8 @@ public class SshClientAdapter implements Runnable { } public void start(ChannelHandlerContext ctx) { - if(this.ctx != null) return; // context is already associated. - + if (this.ctx != null) + return; // context is already associated. this.ctx = ctx; new Thread(this).start(); } 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 index 8a0a9cd80e..35e17a2a3e 100644 --- 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 @@ -35,8 +35,13 @@ public class NetconfConfigUtil { 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(BundleContext context) { - return extractSomeNetconfAddress(context, InfixProp.tcp); + public static InetSocketAddress extractTCPNetconfAddress(BundleContext context, String exceptionMessageIfNotFound) { + + Optional inetSocketAddressOptional = extractSomeNetconfAddress(context, InfixProp.tcp); + if (inetSocketAddressOptional.isPresent() == false) { + throw new IllegalStateException("Netconf tcp address not found." + exceptionMessageIfNotFound); + } + return inetSocketAddressOptional.get(); } public static Optional extractSSHNetconfAddress(BundleContext context) {