2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
9 package org.opendaylight.controller.netconf.persist.impl;
11 import com.google.common.annotations.VisibleForTesting;
12 import java.io.IOException;
13 import java.util.ArrayList;
14 import java.util.Collections;
15 import java.util.List;
16 import java.util.ListIterator;
17 import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder;
18 import org.opendaylight.controller.config.persist.api.Persister;
19 import org.opendaylight.controller.config.persist.api.PropertiesProvider;
20 import org.opendaylight.controller.config.persist.api.StorageAdapter;
21 import org.opendaylight.controller.netconf.persist.impl.osgi.ConfigPersisterActivator;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
26 * {@link Persister} implementation that delegates persisting functionality to
27 * underlying {@link Persister} storages. Each storage has unique id, class, readonly value.
29 * Storage adapters are low level persisters that do the heavy lifting for this
30 * class. Instances of storage adapters can be injected directly via constructor
31 * or instantiated from a full name of its class provided in a properties file.
33 * Example configuration:<pre>
34 netconf.config.persister.active=2,3
35 # read startup configuration
36 netconf.config.persister.1.storageAdapterClass=org.opendaylight.controller.config.persist.storage.directory.xml.XmlDirectoryStorageAdapter
37 netconf.config.persister.1.properties.fileStorage=configuration/initial/
39 netconf.config.persister.2.storageAdapterClass=org.opendaylight.controller.config.persist.storage.file.xml.XmlFileStorageAdapter
40 netconf.config.persister.2.readonly=true
41 netconf.config.persister.2.properties.fileStorage=configuration/current/controller.config.1.xml
43 netconf.config.persister.3.storageAdapterClass=org.opendaylight.controller.config.persist.storage.file.xml.XmlFileStorageAdapter
44 netconf.config.persister.3.properties.fileStorage=configuration/current/controller.config.2.xml
45 netconf.config.persister.3.properties.numberOfBackups=3
48 * During server startup {@link ConfigPersisterNotificationHandler} requests last snapshot from underlying storages.
49 * Each storage can respond by giving snapshot or absent response.
50 * The {@link #loadLastConfigs()} will search for first non-absent response from storages ordered backwards as user
51 * specified (first '3', then '2').
53 * When a commit notification is received, '2' will be omitted because readonly flag is set to true, so
54 * only '3' will have a chance to persist new configuration. If readonly was false or not specified, both storage adapters
55 * would be called in order specified by 'netconf.config.persister' property.
58 public final class PersisterAggregator implements Persister {
59 private static final Logger LOG = LoggerFactory.getLogger(PersisterAggregator.class);
61 public static class PersisterWithConfiguration {
63 private final Persister storage;
64 private final boolean readOnly;
66 public PersisterWithConfiguration(Persister storage, boolean readOnly) {
67 this.storage = storage;
68 this.readOnly = readOnly;
72 public Persister getStorage() {
77 public boolean isReadOnly() {
82 public String toString() {
83 return "PersisterWithConfiguration{" +
84 "storage=" + storage +
85 ", readOnly=" + readOnly +
90 private static PersisterWithConfiguration loadConfiguration(final String index, final PropertiesProvider propertiesProvider) {
92 String classKey = index + "." + ConfigPersisterActivator.STORAGE_ADAPTER_CLASS_PROP_SUFFIX;
93 String storageAdapterClass = propertiesProvider.getProperty(classKey);
94 StorageAdapter storageAdapter;
95 if (storageAdapterClass == null || storageAdapterClass.equals("")) {
96 throw new IllegalStateException("No persister is defined in " +
97 propertiesProvider.getFullKeyForReporting(classKey)
98 + " property. Persister is not operational");
102 Class<?> clazz = Class.forName(storageAdapterClass);
103 boolean implementsCorrectIfc = StorageAdapter.class.isAssignableFrom(clazz);
104 if (!implementsCorrectIfc) {
105 throw new IllegalArgumentException("Storage adapter " + clazz + " does not implement " + StorageAdapter.class);
107 storageAdapter = StorageAdapter.class.cast(clazz.newInstance());
109 boolean readOnly = false;
110 String readOnlyProperty = propertiesProvider.getProperty(index + "." + "readonly");
111 if (readOnlyProperty != null && readOnlyProperty.equals("true")) {
115 PropertiesProviderAdapterImpl innerProvider = new PropertiesProviderAdapterImpl(propertiesProvider, index);
116 Persister storage = storageAdapter.instantiate(innerProvider);
117 return new PersisterWithConfiguration(storage, readOnly);
118 } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
119 throw new IllegalArgumentException("Unable to instantiate storage adapter from " + storageAdapterClass, e);
124 * Persisters ordered by 'netconf.config.persister' property.
126 private final List<PersisterWithConfiguration> persisterWithConfigurations;
128 public PersisterAggregator(List<PersisterWithConfiguration> persisterWithConfigurations) {
129 this.persisterWithConfigurations = persisterWithConfigurations;
133 public static PersisterAggregator createFromProperties(PropertiesProvider propertiesProvider) {
134 List<PersisterWithConfiguration> persisterWithConfigurations = new ArrayList<>();
135 String prefixes = propertiesProvider.getProperty("active");
136 if (prefixes!=null && !prefixes.isEmpty()) {
137 String [] keys = prefixes.split(",");
138 for (String index: keys) {
139 persisterWithConfigurations.add(PersisterAggregator.loadConfiguration(index, propertiesProvider));
142 LOG.debug("Initialized persister with following adapters {}", persisterWithConfigurations);
143 return new PersisterAggregator(persisterWithConfigurations);
147 public void persistConfig(ConfigSnapshotHolder holder) throws IOException {
148 for (PersisterWithConfiguration persisterWithConfiguration: persisterWithConfigurations){
149 if (!persisterWithConfiguration.readOnly){
150 LOG.debug("Calling {}.persistConfig", persisterWithConfiguration.getStorage());
151 persisterWithConfiguration.getStorage().persistConfig(holder);
157 * @return last non-empty result from input persisters
160 public List<ConfigSnapshotHolder> loadLastConfigs() {
161 // iterate in reverse order
162 ListIterator<PersisterWithConfiguration> li = persisterWithConfigurations.listIterator(persisterWithConfigurations.size());
163 while(li.hasPrevious()) {
164 PersisterWithConfiguration persisterWithConfiguration = li.previous();
165 List<ConfigSnapshotHolder> configs = null;
167 configs = persisterWithConfiguration.storage.loadLastConfigs();
168 } catch (IOException e) {
169 throw new RuntimeException("Error while calling loadLastConfig on " + persisterWithConfiguration, e);
171 if (!configs.isEmpty()) {
172 LOG.debug("Found non empty configs using {}:{}", persisterWithConfiguration, configs);
176 // no storage had an answer
177 LOG.debug("No non-empty list of configuration snapshots found");
178 return Collections.emptyList();
182 List<PersisterWithConfiguration> getPersisterWithConfigurations() {
183 return persisterWithConfigurations;
187 public void close() {
188 RuntimeException lastException = null;
189 for (PersisterWithConfiguration persisterWithConfiguration: persisterWithConfigurations){
191 persisterWithConfiguration.storage.close();
192 }catch(RuntimeException e) {
193 LOG.error("Error while closing {}", persisterWithConfiguration.storage, e);
194 if (lastException == null){
197 lastException.addSuppressed(e);
201 if (lastException != null){
207 public String toString() {
208 return "PersisterAggregator{" +
209 "persisterWithConfigurations=" + persisterWithConfigurations +