File and Directory based xml adapters are now available.
Only File xml adapter is used so far to persist current configuration.
Directory based persister is still using plain text to keep backwards compatibility.
Change-Id: If1a83701ce23d36313c943e9fe49bd4e704afe27
Signed-off-by: Maros Marsalek <mmarsale@cisco.com>
}
private void checkFileConsistency(){
- checkState(inCapabilities, "File {} is missing delimiters in this order: {}", fileNameForReporting,
+ checkState(inCapabilities, "File %s is missing delimiters in this order: %s", fileNameForReporting,
Arrays.asList(DirectoryPersister.MODULES_START,
DirectoryPersister.SERVICES_START,
DirectoryPersister.CAPABILITIES_START));
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <artifactId>config-subsystem</artifactId>
+ <groupId>org.opendaylight.controller</groupId>
+ <version>0.2.3-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <artifactId>config-persister-directory-xml-adapter</artifactId>
+ <name>${project.artifactId}</name>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <!-- compile dependencies -->
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>config-persister-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>config-persister-file-xml-adapter</artifactId>
+ <version>${config.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.eclipse.persistence</groupId>
+ <artifactId>org.eclipse.persistence.moxy</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.persistence</groupId>
+ <artifactId>org.eclipse.persistence.core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ </dependency>
+
+ <!-- test dependencies -->
+ <dependency>
+ <groupId>org.opendaylight.bgpcep</groupId>
+ <artifactId>mockito-configuration</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- workaround for creating version according to OSGi specification (major.minor.micro[.qualifier] -->
+ <plugin>
+ <groupId>org.codehaus.groovy.maven</groupId>
+ <artifactId>gmaven-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>execute</goal>
+ </goals>
+ <configuration>
+ <source>
+ System.setProperty("osgiversion", "${project.version}".replace('-', '.'))
+ </source>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Fragment-Host>${project.groupId}.config-persister-impl;bundle-version=${osgiversion}
+ </Fragment-Host>
+ <Provide-Capability>org.opendaylight.controller.config.persister.storage.adapter
+ </Provide-Capability>
+ <Import-Package>
+ com.google.common.base,
+ com.google.common.io,
+ org.apache.commons.io,
+ org.opendaylight.controller.config.persist.api,
+ org.slf4j,
+ com.google.common.collect,
+ javax.xml.bind,
+ javax.xml.bind.annotation,
+ javax.xml.transform,
+ javax.xml.transform.stream,
+ org.eclipse.persistence.jaxb,
+ org.apache.commons.lang3
+ </Import-Package>
+ <Private-Package>
+ org.opendaylight.controller.config.persist.storage.file.xml.model,
+ </Private-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--- /dev/null
+/*
+ * 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.xml;
+
+import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder;
+import org.opendaylight.controller.config.persist.api.Persister;
+import org.opendaylight.controller.config.persist.storage.file.xml.model.ConfigSnapshot;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.SortedSet;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+public class XmlDirectoryPersister implements Persister {
+ private static final Logger logger = LoggerFactory.getLogger(XmlDirectoryPersister.class);
+
+ private final File storage;
+
+ public XmlDirectoryPersister(File storage) {
+ checkArgument(storage.exists() && storage.isDirectory(), "Storage directory does not exist: " + storage);
+ this.storage = storage;
+ }
+
+ @Override
+ public void persistConfig(ConfigSnapshotHolder holder) throws IOException {
+ throw new UnsupportedOperationException("This adapter is read only. Please set readonly=true on " + getClass());
+ }
+
+ @Override
+ public List<ConfigSnapshotHolder> loadLastConfigs() throws IOException {
+ File[] filesArray = storage.listFiles();
+ if (filesArray == null || filesArray.length == 0) {
+ return Collections.emptyList();
+ }
+ List<File> sortedFiles = new ArrayList<>(Arrays.asList(filesArray));
+ Collections.sort(sortedFiles);
+ // combine all found files
+ logger.debug("Reading files in following order: {}", sortedFiles);
+
+ List<ConfigSnapshotHolder> result = new ArrayList<>();
+ for (File file : sortedFiles) {
+ logger.trace("Adding file '{}' to combined result", file);
+ ConfigSnapshotHolder h = fromXmlSnapshot(file);
+ result.add(h);
+ }
+ return result;
+ }
+
+ private ConfigSnapshotHolder fromXmlSnapshot(File file) {
+ try {
+ JAXBContext jaxbContext = JAXBContext.newInstance(ConfigSnapshot.class);
+ Unmarshaller um = jaxbContext.createUnmarshaller();
+
+ return asHolder((ConfigSnapshot) um.unmarshal(file));
+ } catch (JAXBException e) {
+ logger.warn("Unable to restore configuration snapshot from {}", file, e);
+ throw new IllegalStateException("Unable to restore configuration snapshot from " + file, e);
+ }
+ }
+
+ private ConfigSnapshotHolder asHolder(final ConfigSnapshot unmarshalled) {
+ return new ConfigSnapshotHolder() {
+ @Override
+ public String getConfigSnapshot() {
+ return unmarshalled.getConfigSnapshot();
+ }
+
+ @Override
+ public SortedSet<String> getCapabilities() {
+ return unmarshalled.getCapabilities();
+ }
+
+ @Override
+ public String toString() {
+ return unmarshalled.toString();
+ }
+ };
+ }
+
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public String toString() {
+ final StringBuffer sb = new StringBuffer("XmlDirectoryPersister{");
+ sb.append("storage=").append(storage);
+ sb.append('}');
+ return sb.toString();
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * 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.xml;
+
+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 XmlDirectoryStorageAdapter implements StorageAdapter {
+ private static final Logger logger = LoggerFactory.getLogger(XmlDirectoryStorageAdapter.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 XmlDirectoryPersister(storage);
+ }
+
+}
--- /dev/null
+/*
+ * 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.xml;
+
+import org.junit.Test;
+import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+import java.util.SortedSet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class DirectoryStorageAdapterTest {
+ XmlDirectoryPersister tested;
+
+ @Test
+ public void testEmptyDirectory() throws Exception {
+ File folder = new File("target/emptyFolder");
+ folder.mkdir();
+ tested = new XmlDirectoryPersister((folder));
+ assertEquals(Collections.<ConfigSnapshotHolder>emptyList(), tested.loadLastConfigs());
+
+ try {
+ tested.persistConfig(new ConfigSnapshotHolder() {
+ @Override
+ public String getConfigSnapshot() {
+ throw new RuntimeException();
+ }
+
+ @Override
+ public SortedSet<String> 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 XmlDirectoryPersister((folder));
+ List<ConfigSnapshotHolder> results = tested.loadLastConfigs();
+ assertEquals(1, results.size());
+ ConfigSnapshotHolder result = results.get(0);
+ assertResult(result, "<config>1</config>", "cap1&rev", "cap2", "capa a");
+ }
+
+ private void assertResult(ConfigSnapshotHolder result, String s, String... caps) {
+ assertEquals(s, result.getConfigSnapshot().replaceAll("\\s", ""));
+ int i = 0;
+ for (String capFromSnapshot : result.getCapabilities()) {
+ assertEquals(capFromSnapshot, caps[i++]);
+ }
+ }
+
+ @Test
+ public void testTwoFiles() throws Exception {
+ File folder = getFolder("twoFiles");
+ tested = new XmlDirectoryPersister((folder));
+ List<ConfigSnapshotHolder> results = tested.loadLastConfigs();
+ assertEquals(2, results.size());
+
+ assertResult(results.get(0), "<config>1</config>", "cap1-a", "cap2-a", "capa a-a");
+ assertResult(results.get(1), "<config>2</config>", "cap1-b", "cap2-b", "capa a-b");
+
+ }
+
+}
--- /dev/null
+<snapshot>
+ <required-capabilities>
+ <capability>cap1&rev</capability>
+ <capability>cap2</capability>
+ <capability>capa a</capability>
+ </required-capabilities>
+ <configuration>
+ <config>1</config>
+ </configuration>
+</snapshot>
\ No newline at end of file
--- /dev/null
+<snapshot>
+ <required-capabilities>
+ <capability>cap1-a</capability>
+ <capability>cap2-a</capability>
+ <capability>capa a-a</capability>
+ </required-capabilities>
+ <configuration>
+ <config>1</config>
+ </configuration>
+</snapshot>
\ No newline at end of file
--- /dev/null
+<snapshot>
+ <required-capabilities>
+ <capability>cap1-b</capability>
+ <capability>cap2-b</capability>
+ <capability>capa a-b</capability>
+ </required-capabilities>
+ <configuration>
+ <config>2</config>
+ </configuration>
+</snapshot>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <artifactId>config-subsystem</artifactId>
+ <groupId>org.opendaylight.controller</groupId>
+ <version>0.2.3-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <artifactId>config-persister-file-xml-adapter</artifactId>
+ <name>${project.artifactId}</name>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <!-- compile dependencies -->
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>config-persister-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.eclipse.persistence</groupId>
+ <artifactId>org.eclipse.persistence.moxy</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.persistence</groupId>
+ <artifactId>org.eclipse.persistence.core</artifactId>
+ </dependency>
+
+ <!-- test dependencies -->
+ <dependency>
+ <groupId>org.opendaylight.bgpcep</groupId>
+ <artifactId>mockito-configuration</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- workaround for creating version according to OSGi specification (major.minor.micro[.qualifier] -->
+ <plugin>
+ <groupId>org.codehaus.groovy.maven</groupId>
+ <artifactId>gmaven-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>execute</goal>
+ </goals>
+ <configuration>
+ <source>
+ System.setProperty("osgiversion", "${project.version}".replace('-', '.'))
+ </source>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Fragment-Host>${project.groupId}.config-persister-impl;bundle-version=${osgiversion}
+ </Fragment-Host>
+ <Provide-Capability>org.opendaylight.controller.config.persister.storage.adapter
+ </Provide-Capability>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.config.persist.storage.file.xml;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+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.xml.model.Config;
+import org.opendaylight.controller.config.persist.storage.file.xml.model.ConfigSnapshot;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.SortedSet;
+
+/**
+ * StorageAdapter that stores configuration in an xml file.
+ */
+public class XmlFileStorageAdapter implements StorageAdapter, Persister {
+ private static final Logger logger = LoggerFactory.getLogger(XmlFileStorageAdapter.class);
+
+ public static final String FILE_STORAGE_PROP = "fileStorage";
+ public static final String NUMBER_OF_BACKUPS = "numberOfBackups";
+
+ private static Integer numberOfStoredBackups;
+ private File storage;
+
+ @Override
+ 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();
+ if (parentFile.exists() == false) {
+ logger.debug("Creating parent folders {}", parentFile);
+ parentFile.mkdirs();
+ }
+ if (storage.exists() == false) {
+ logger.debug("Storage file does not exist, creating empty file");
+ try {
+ boolean result = storage.createNewFile();
+ if (result == false)
+ throw new RuntimeException("Unable to create storage file " + storage);
+ } catch (IOException e) {
+ throw new RuntimeException("Unable to create storage file " + storage, e);
+ }
+ }
+ if (numberOfStoredBackups == 0) {
+ throw new RuntimeException(NUMBER_OF_BACKUPS
+ + " property should be either set to positive value, or ommited. Can not be set to 0.");
+ }
+ setFileStorage(storage);
+ return this;
+ }
+
+ @VisibleForTesting
+ public void setFileStorage(File storage) {
+ this.storage = storage;
+ }
+
+ @VisibleForTesting
+ public void setNumberOfBackups(Integer numberOfBackups) {
+ numberOfStoredBackups = numberOfBackups;
+ }
+
+ 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 = propertiesProvider.getProperty(NUMBER_OF_BACKUPS);
+ if (numberOfBackupsAsString != null) {
+ numberOfStoredBackups = Integer.valueOf(numberOfBackupsAsString);
+ } else {
+ numberOfStoredBackups = Integer.MAX_VALUE;
+ }
+ logger.trace("Property {} set to {}", NUMBER_OF_BACKUPS, numberOfStoredBackups);
+ return result;
+ }
+
+ @Override
+ public void persistConfig(ConfigSnapshotHolder holder) throws IOException {
+ Preconditions.checkNotNull(storage, "Storage file is null");
+
+ Config cfg = Config.fromXml(storage);
+ cfg.addConfigSnapshot(ConfigSnapshot.fromConfigSnapshot(holder), numberOfStoredBackups);
+ cfg.toXml(storage);
+ }
+
+ @Override
+ public List<ConfigSnapshotHolder> loadLastConfigs() throws IOException {
+ Preconditions.checkNotNull(storage, "Storage file is null");
+
+ if (!storage.exists()) {
+ return Collections.emptyList();
+ }
+
+ Optional<ConfigSnapshot> lastSnapshot = Config.fromXml(storage).getLastSnapshot();
+
+ if (lastSnapshot.isPresent())
+ return Lists.newArrayList(toConfigSnapshot(lastSnapshot.get()));
+ else
+ return Collections.emptyList();
+ }
+
+
+ public ConfigSnapshotHolder toConfigSnapshot(final ConfigSnapshot configSnapshot) {
+ return new ConfigSnapshotHolder() {
+ @Override
+ public String getConfigSnapshot() {
+ return configSnapshot.getConfigSnapshot();
+ }
+
+ @Override
+ public SortedSet<String> getCapabilities() {
+ return configSnapshot.getCapabilities();
+ }
+
+ @Override
+ public String toString() {
+ return configSnapshot.toString();
+ }
+ };
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public String toString() {
+ return "XmlFileStorageAdapter [storage=" + storage + "]";
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.persist.storage.file.xml.model;
+
+import javax.xml.bind.ValidationEventHandler;
+import javax.xml.bind.annotation.DomHandler;
+import javax.xml.transform.Source;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+import java.io.StringReader;
+import java.io.StringWriter;
+
+class CapabilityHandler implements DomHandler<String, StreamResult> {
+
+ private static final String START_TAG = "<capability>";
+ private static final String END_TAG = "</capability>";
+
+ private StringWriter xmlWriter = new StringWriter();
+
+ public StreamResult createUnmarshaller(ValidationEventHandler errorHandler) {
+ xmlWriter.getBuffer().setLength(0);
+ return new StreamResult(xmlWriter);
+ }
+
+ public String getElement(StreamResult rt) {
+ String xml = rt.getWriter().toString();
+ int beginIndex = xml.indexOf(START_TAG) + START_TAG.length();
+ int endIndex = xml.indexOf(END_TAG);
+ return xml.substring(beginIndex, endIndex);
+ }
+
+ public Source marshal(String n, ValidationEventHandler errorHandler) {
+ try {
+ String xml = START_TAG + n.trim() + END_TAG;
+ StringReader xmlReader = new StringReader(xml);
+ return new StreamSource(xmlReader);
+ } catch(Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.persist.storage.file.xml.model;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Optional;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+@XmlRootElement(name = "persisted-snapshots")
+public final class Config {
+
+ private List<ConfigSnapshot> snapshots;
+
+ Config(List<ConfigSnapshot> snapshots) {
+ this.snapshots = snapshots;
+ }
+
+ public Config() {
+ this.snapshots = Lists.newArrayList();
+ }
+
+ @XmlElement(name = "snapshot")
+ @XmlElementWrapper(name = "snapshots")
+ public List<ConfigSnapshot> getSnapshots() {
+ return snapshots;
+ }
+
+ public void setSnapshots(List<ConfigSnapshot> snapshots) {
+ this.snapshots = snapshots;
+ }
+
+ public void toXml(File to) {
+ try {
+
+ // TODO Moxy has to be used instead of default jaxb impl due to a bug
+ // default implementation has a bug that prevents from serializing xml in a string
+ JAXBContext jaxbContext = org.eclipse.persistence.jaxb.JAXBContextFactory.createContext(new Class[]{Config.class}, null);
+
+ Marshaller marshaller = jaxbContext.createMarshaller();
+
+ marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
+
+ marshaller.marshal(this, to);
+ } catch (JAXBException e) {
+ throw new PersistException("Unable to persist configuration", e);
+ }
+ }
+
+ public static Config fromXml(File from) {
+ if(isEmpty(from))
+ return new Config();
+
+ try {
+ JAXBContext jaxbContext = JAXBContext.newInstance(Config.class);
+ Unmarshaller um = jaxbContext.createUnmarshaller();
+
+ return (Config) um.unmarshal(from);
+ } catch (JAXBException e) {
+ throw new PersistException("Unable to restore configuration", e);
+ }
+ }
+
+ private static boolean isEmpty(File from) {
+ return from.length() == 0 || isBlank(from);
+ }
+
+ private static boolean isBlank(File from) {
+ try {
+ return StringUtils.isBlank(Files.toString(from, Charsets.UTF_8));
+ } catch (IOException e) {
+ throw new IllegalStateException("Unexpected error reading file" + from, e);
+ }
+ }
+
+ public Optional<ConfigSnapshot> getLastSnapshot() {
+ ConfigSnapshot last = Iterables.getLast(snapshots, null);
+ return last == null ? Optional.<ConfigSnapshot>absent() : Optional.of(last);
+ }
+
+ public void addConfigSnapshot(ConfigSnapshot snap, int numberOfStoredBackups) {
+ if(shouldReplaceLast(numberOfStoredBackups) && snapshots.isEmpty() == false) {
+ snapshots.remove(0);
+ }
+ snapshots.add(snap);
+ }
+
+ private boolean shouldReplaceLast(int numberOfStoredBackups) {
+ return numberOfStoredBackups == snapshots.size();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.persist.storage.file.xml.model;
+
+import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder;
+
+import javax.xml.bind.annotation.XmlAnyElement;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import java.util.SortedSet;
+
+@XmlRootElement(name = "snapshot")
+public class ConfigSnapshot {
+
+ private String configSnapshot;
+ private SortedSet<String> capabilities;
+
+ ConfigSnapshot(String configXml, SortedSet<String> capabilities) {
+ this.configSnapshot = configXml;
+ this.capabilities = capabilities;
+ }
+
+ public ConfigSnapshot() {
+ }
+
+ public static ConfigSnapshot fromConfigSnapshot(ConfigSnapshotHolder cfg) {
+ return new ConfigSnapshot(cfg.getConfigSnapshot(), cfg.getCapabilities());
+ }
+
+ @XmlAnyElement(SnapshotHandler.class)
+ public String getConfigSnapshot() {
+ return configSnapshot;
+ }
+
+ public void setConfigSnapshot(String configSnapshot) {
+ this.configSnapshot = configSnapshot;
+ }
+
+ @XmlElement(name = "capability")
+ @XmlElementWrapper(name = "required-capabilities")
+ public SortedSet<String> getCapabilities() {
+ return capabilities;
+ }
+
+ public void setCapabilities(SortedSet<String> capabilities) {
+ this.capabilities = capabilities;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuffer sb = new StringBuffer("ConfigSnapshot{");
+ sb.append("configSnapshot='").append(configSnapshot).append('\'');
+ sb.append(", capabilities=").append(capabilities);
+ sb.append('}');
+ return sb.toString();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.persist.storage.file.xml.model;
+
+final class PersistException extends RuntimeException {
+
+ public PersistException(String s, Exception e) {
+ super(s, e);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.persist.storage.file.xml.model;
+
+import javax.xml.bind.ValidationEventHandler;
+import javax.xml.bind.annotation.DomHandler;
+import javax.xml.transform.Source;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+import java.io.StringReader;
+import java.io.StringWriter;
+
+class SnapshotHandler implements DomHandler<String, StreamResult> {
+
+ private static final String START_TAG = "<configuration>";
+ private static final String END_TAG = "</configuration>";
+
+ private StringWriter xmlWriter = new StringWriter();
+
+ public StreamResult createUnmarshaller(ValidationEventHandler errorHandler) {
+ xmlWriter.getBuffer().setLength(0);
+ return new StreamResult(xmlWriter);
+ }
+
+ public String getElement(StreamResult rt) {
+ String xml = rt.getWriter().toString();
+ int beginIndex = xml.indexOf(START_TAG) + START_TAG.length();
+ int endIndex = xml.indexOf(END_TAG);
+ return xml.substring(beginIndex, endIndex);
+ }
+
+ public Source marshal(String n, ValidationEventHandler errorHandler) {
+ try {
+ String xml = START_TAG + n.trim() + END_TAG;
+ StringReader xmlReader = new StringReader(xml);
+ return new StreamSource(xmlReader);
+ } catch(Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.config.persist.storage.file.xml;
+
+import com.google.common.base.Charsets;
+import junit.framework.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import static junit.framework.Assert.assertFalse;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+public class FileStorageAdapterTest {
+
+ private static int i;
+ private File file;
+
+ @Before
+ public void setUp() throws Exception {
+ file = Files.createTempFile("testFilePersist", ".txt").toFile();
+ if (!file.exists())
+ return;
+ com.google.common.io.Files.write("", file, Charsets.UTF_8);
+ i = 1;
+ }
+
+ @Test
+ public void testFileAdapter() throws Exception {
+ XmlFileStorageAdapter storage = new XmlFileStorageAdapter();
+ storage.setFileStorage(file);
+ storage.setNumberOfBackups(Integer.MAX_VALUE);
+ final ConfigSnapshotHolder holder = new ConfigSnapshotHolder() {
+ @Override
+ public String getConfigSnapshot() {
+ return createConfig();
+ }
+
+ @Override
+ public SortedSet<String> getCapabilities() {
+ return createCaps();
+ }
+ };
+ storage.persistConfig(holder);
+
+ storage.persistConfig(holder);
+
+ assertEquals(27, com.google.common.io.Files.readLines(file, Charsets.UTF_8).size());
+ List<ConfigSnapshotHolder> lastConf = storage.loadLastConfigs();
+ assertEquals(1, lastConf.size());
+ ConfigSnapshotHolder configSnapshotHolder = lastConf.get(0);
+ assertEquals("<config>2</config>",
+ configSnapshotHolder.getConfigSnapshot().replaceAll("\\s", ""));
+ assertEquals(createCaps(), configSnapshotHolder.getCapabilities());
+
+ storage = new XmlFileStorageAdapter();
+ storage.setFileStorage(file);
+ storage.setNumberOfBackups(Integer.MAX_VALUE);
+
+ List<ConfigSnapshotHolder> last = storage.loadLastConfigs();
+ Assert.assertEquals(createCaps(), last.get(0).getCapabilities());
+ }
+
+ private SortedSet<String> createCaps() {
+ SortedSet<String> caps = new TreeSet<>();
+
+ caps.add("cap1" + i);
+ caps.add("cap2" + i);
+ caps.add("urn:opendaylight:params:xml:ns:yang:controller:netty?module=netty&revision=2013-11-19" + i);
+ caps.add("capaaaa as dasfasdf s2" + i);
+ return caps;
+ }
+
+ @Test
+ public void testFileAdapterOneBackup() throws Exception {
+ XmlFileStorageAdapter storage = new XmlFileStorageAdapter();
+ storage.setFileStorage(file);
+ storage.setNumberOfBackups(1);
+ final ConfigSnapshotHolder holder = new ConfigSnapshotHolder() {
+ @Override
+ public String getConfigSnapshot() {
+ return createConfig();
+ }
+
+ @Override
+ public SortedSet<String> getCapabilities() {
+ return createCaps();
+ }
+ };
+ storage.persistConfig(holder);
+
+ storage.persistConfig(holder);
+
+ assertEquals(16, com.google.common.io.Files.readLines(file, Charsets.UTF_8).size());
+
+ List<ConfigSnapshotHolder> lastConf = storage.loadLastConfigs();
+ assertEquals(1, lastConf.size());
+ ConfigSnapshotHolder configSnapshotHolder = lastConf.get(0);
+ assertEquals("<config>2</config>",
+ configSnapshotHolder.getConfigSnapshot().replaceAll("\\s", ""));
+ }
+
+ @Test
+ public void testFileAdapterOnlyTwoBackups() throws Exception {
+ XmlFileStorageAdapter storage = new XmlFileStorageAdapter();
+ storage.setFileStorage(file);
+ storage.setNumberOfBackups(2);
+ final ConfigSnapshotHolder holder = new ConfigSnapshotHolder() {
+ @Override
+ public String getConfigSnapshot() {
+ return createConfig();
+ }
+
+ @Override
+ public SortedSet<String> getCapabilities() {
+ return createCaps();
+ }
+ };
+ storage.persistConfig(holder);
+
+ storage.persistConfig(holder);
+ storage.persistConfig(holder);
+
+ List<String> readLines = com.google.common.io.Files.readLines(file, Charsets.UTF_8);
+ assertEquals(27, readLines.size());
+
+ List<ConfigSnapshotHolder> lastConf = storage.loadLastConfigs();
+ assertEquals(1, lastConf.size());
+ ConfigSnapshotHolder configSnapshotHolder = lastConf.get(0);
+ assertEquals("<config>3</config>",
+ configSnapshotHolder.getConfigSnapshot().replaceAll("\\s", ""));
+ assertFalse(readLines.contains(holder.getConfigSnapshot()));
+ }
+
+ @Test
+ public void testNoLastConfig() throws Exception {
+ File file = Files.createTempFile("testFilePersist", ".txt").toFile();
+ if (!file.exists())
+ return;
+ XmlFileStorageAdapter storage = new XmlFileStorageAdapter();
+ storage.setFileStorage(file);
+
+ List<ConfigSnapshotHolder> elementOptional = storage.loadLastConfigs();
+ assertThat(elementOptional.size(), is(0));
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testNoProperties() throws Exception {
+ XmlFileStorageAdapter storage = new XmlFileStorageAdapter();
+ storage.loadLastConfigs();
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testNoProperties2() throws Exception {
+ XmlFileStorageAdapter storage = new XmlFileStorageAdapter();
+ storage.persistConfig(new ConfigSnapshotHolder() {
+ @Override
+ public String getConfigSnapshot() {
+ return Mockito.mock(String.class);
+ }
+
+ @Override
+ public SortedSet<String> getCapabilities() {
+ return new TreeSet<>();
+ }
+ } );
+ }
+
+ static String createConfig() {
+ return "<config>" + i++ + "</config>";
+ }
+
+}
<module>config-util</module>
<module>config-persister-api</module>
<module>config-persister-file-adapter</module>
+ <module>config-persister-file-xml-adapter</module>
<module>yang-jmx-generator</module>
<module>yang-jmx-generator-plugin</module>
<module>yang-store-api</module>
<module>netty-event-executor-config</module>
<module>netty-timer-config</module>
<module>config-persister-directory-adapter</module>
+ <module>config-persister-directory-xml-adapter</module>
<module>yang-test-plugin</module>
</modules>
<artifactId>config-persister-file-adapter</artifactId>
<version>${config.version}</version>
</dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>config-persister-directory-adapter</artifactId>
- <version>${config.version}</version>
- </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>config-persister-file-xml-adapter</artifactId>
+ <version>${config.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>config-persister-directory-adapter</artifactId>
+ <version>${config.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>config-persister-directory-xml-adapter</artifactId>
+ <version>${config.version}</version>
+ </dependency>
<!-- Netconf -->
<dependency>
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
+#netconf.config.persister.3.storageAdapterClass=org.opendaylight.controller.config.persist.storage.directory.xml.XmlDirectoryStorageAdapter
+#netconf.config.persister.3.properties.directoryStorage=configuration/initialXml/
+#netconf.config.persister.3.readonly=true
+
+#netconf.config.persister.4.storageAdapterClass=org.opendaylight.controller.config.persist.storage.file.FileStorageAdapter
+#netconf.config.persister.4.properties.fileStorage=configuration/current/controller.currentconfig.txt
+#netconf.config.persister.4.properties.numberOfBackups=1
+
+netconf.config.persister.2.storageAdapterClass=org.opendaylight.controller.config.persist.storage.file.xml.XmlFileStorageAdapter
+netconf.config.persister.2.properties.fileStorage=configuration/current/controller.currentconfig.xml
netconf.config.persister.2.properties.numberOfBackups=1
-/**
- * @author Maros Marsalek
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
*
- * 12 2013
- *
- * Copyright (c) 2012 by Cisco Systems, Inc.
- * All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
*/
package org.opendaylight.controller.netconf.confignetconfconnector.mapping.attributes;
Preconditions.checkArgument(refNameToInstance != null, "No serviceInstances mapped to " + serviceName + " , "
+ serviceNameToRefNameToInstance.keySet());
- ServiceInstance serviceInstance = ServiceInstance.fromString(refNameToInstance.get(refName));
+ String instanceId = refNameToInstance.get(refName);
+ Preconditions.checkArgument(instanceId != null, "No serviceInstances mapped to " + serviceName + ":"
+ + refName + ", " + serviceNameToRefNameToInstance.keySet());
+
+ ServiceInstance serviceInstance = ServiceInstance.fromString(instanceId);
Preconditions.checkArgument(serviceInstance != null, "No serviceInstance mapped to " + refName
+ " under service name " + serviceName + " , " + refNameToInstance.keySet());
return serviceInstance;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
import org.apache.commons.lang3.StringUtils;
import org.junit.Before;
import org.junit.Ignore;
import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.Set;
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.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
XmlElement modulesElement = XmlElement.fromDomElement(response).getOnlyChildElement("data")
.getOnlyChildElement("modules");
- XmlElement configAttributeType = null;
+ List<String> expectedValues = Lists.newArrayList("default-string", "configAttributeType");
+ Set<String> configAttributeType = Sets.newHashSet();
+
for (XmlElement moduleElement : modulesElement.getChildElements("module")) {
for (XmlElement type : moduleElement.getChildElements("type")) {
if (type.getAttribute(XmlUtil.XMLNS_ATTRIBUTE_KEY).equals("") == false) {
- configAttributeType = type;
+ configAttributeType.add(type.getTextContent());
}
}
}
- // TODO verify if should be default value
- assertEquals("default-string", configAttributeType.getTextContent());
+ for (String expectedValue : expectedValues) {
+ assertTrue(configAttributeType.contains(expectedValue));
+ }
}
private Map<String, Map<String, ModuleMXBeanEntry>> getMbes() throws Exception {
</dependency>
<dependency>
<groupId>org.opendaylight.controller</groupId>
- <artifactId>config-persister-file-adapter</artifactId>
+ <artifactId>config-persister-file-xml-adapter</artifactId>
+ <scope>test</scope>
+ <version>${config.version}</version>
</dependency>
<!-- test dependencies -->
</dependency>
<dependency>
<groupId>org.opendaylight.controller</groupId>
- <artifactId>config-persister-directory-adapter</artifactId>
- <version>${parent.version}</version>
+ <artifactId>config-persister-directory-xml-adapter</artifactId>
+ <version>${config.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
* Example configuration:<pre>
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.storageAdapterClass=org.opendaylight.controller.config.persist.storage.directory.xml.XmlDirectoryStorageAdapter
netconf.config.persister.1.properties.fileStorage=configuration/initial/
netconf.config.persister.2.storageAdapterClass=org.opendaylight.controller.config.persist.storage.file.FileStorageAdapter
import org.junit.Test;
import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder;
import org.opendaylight.controller.config.persist.api.Persister;
-import org.opendaylight.controller.config.persist.storage.file.FileStorageAdapter;
+import org.opendaylight.controller.config.persist.storage.file.xml.XmlFileStorageAdapter;
import org.opendaylight.controller.netconf.persist.impl.osgi.ConfigPersisterActivator;
import org.opendaylight.controller.netconf.persist.impl.osgi.PropertiesProviderBaseImpl;
List<PersisterWithConfiguration> persisters = persisterAggregator.getPersisterWithConfigurations();
assertEquals(1, persisters.size());
PersisterWithConfiguration persister = persisters.get(0);
- assertEquals(FileStorageAdapter.class.getName() ,persister.getStorage().getClass().getName());
+ assertEquals(XmlFileStorageAdapter.class.getName() ,persister.getStorage().getClass().getName());
assertFalse(persister.isReadOnly());
}
netconf.config.persister.active=2
# read startup configuration
-netconf.config.persister.1.storageAdapterClass=org.opendaylight.controller.config.persist.storage.directory.DirectoryStorageAdapter
+netconf.config.persister.1.storageAdapterClass=org.opendaylight.controller.config.persist.storage.directory.xml.XmlDirectoryStorageAdapter
netconf.config.persister.1.properties.directoryStorage=target/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.storageAdapterClass=org.opendaylight.controller.config.persist.storage.file.xml.XmlFileStorageAdapter
netconf.config.persister.2.properties.fileStorage=target/configuration/current/controller.config.2.txt
netconf.config.persister.2.properties.numberOfBackups=3
netconf.config.persister.active=3
-netconf.config.persister.3.storageAdapterClass=org.opendaylight.controller.config.persist.storage.file.FileStorageAdapter
+netconf.config.persister.3.storageAdapterClass=org.opendaylight.controller.config.persist.storage.file.xml.XmlFileStorageAdapter
netconf.config.persister.3.properties.fileStorage=target/configuration/current/controller.config.2.txt
netconf.config.persister.3.properties.numberOfBackups=0