Merge "Bug 1789: Reorder odl-mdsal-clustering feature before odl-restconf"
[controller.git] / opendaylight / netconf / config-persister-impl / src / main / java / org / opendaylight / controller / netconf / persist / impl / PersisterAggregator.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8
9 package org.opendaylight.controller.netconf.persist.impl;
10
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;
24
25 /**
26  * {@link Persister} implementation that delegates persisting functionality to
27  * underlying {@link Persister} storages. Each storage has unique id, class, readonly value.
28  *
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.
32  *
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/
38
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
42
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
46
47  </pre>
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').
52  *
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.
56  *
57  */
58 public final class PersisterAggregator implements Persister {
59     private static final Logger logger = LoggerFactory.getLogger(PersisterAggregator.class);
60
61     public static class PersisterWithConfiguration {
62
63         private final Persister storage;
64         private final boolean readOnly;
65
66         public PersisterWithConfiguration(Persister storage, boolean readOnly) {
67             this.storage = storage;
68             this.readOnly = readOnly;
69         }
70
71         @VisibleForTesting
72         public Persister getStorage() {
73             return storage;
74         }
75
76         @VisibleForTesting
77         public boolean isReadOnly() {
78             return readOnly;
79         }
80
81         @Override
82         public String toString() {
83             return "PersisterWithConfiguration{" +
84                     "storage=" + storage +
85                     ", readOnly=" + readOnly +
86                     '}';
87         }
88     }
89
90     private static PersisterWithConfiguration loadConfiguration(final String index, final PropertiesProvider propertiesProvider) {
91
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");
99         }
100
101         try {
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);
106             }
107             storageAdapter = StorageAdapter.class.cast(clazz.newInstance());
108
109             boolean readOnly = false;
110             String readOnlyProperty = propertiesProvider.getProperty(index + "." + "readonly");
111             if (readOnlyProperty != null && readOnlyProperty.equals("true")) {
112                 readOnly = true;
113             }
114
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);
120         }
121     }
122
123     /**
124      * Persisters ordered by 'netconf.config.persister' property.
125      */
126     private final List<PersisterWithConfiguration> persisterWithConfigurations;
127
128     public PersisterAggregator(List<PersisterWithConfiguration> persisterWithConfigurations) {
129         this.persisterWithConfigurations = persisterWithConfigurations;
130
131     }
132
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));
140             }
141         }
142         logger.debug("Initialized persister with following adapters {}", persisterWithConfigurations);
143         return new PersisterAggregator(persisterWithConfigurations);
144     }
145
146     @Override
147     public void persistConfig(ConfigSnapshotHolder holder) throws IOException {
148         for (PersisterWithConfiguration persisterWithConfiguration: persisterWithConfigurations){
149             if (!persisterWithConfiguration.readOnly){
150                 logger.debug("Calling {}.persistConfig", persisterWithConfiguration.getStorage());
151                 persisterWithConfiguration.getStorage().persistConfig(holder);
152             }
153         }
154     }
155
156     /**
157      * @return last non-empty result from input persisters
158      */
159     @Override
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;
166             try {
167                 configs = persisterWithConfiguration.storage.loadLastConfigs();
168             } catch (IOException e) {
169                 throw new RuntimeException("Error while calling loadLastConfig on " +  persisterWithConfiguration, e);
170             }
171             if (!configs.isEmpty()) {
172                 logger.debug("Found non empty configs using {}:{}", persisterWithConfiguration, configs);
173                 return configs;
174             }
175         }
176         // no storage had an answer
177         logger.debug("No non-empty list of configuration snapshots found");
178         return Collections.emptyList();
179     }
180
181     @VisibleForTesting
182     List<PersisterWithConfiguration> getPersisterWithConfigurations() {
183         return persisterWithConfigurations;
184     }
185
186     @Override
187     public void close() {
188         RuntimeException lastException = null;
189         for (PersisterWithConfiguration persisterWithConfiguration: persisterWithConfigurations){
190             try{
191                 persisterWithConfiguration.storage.close();
192             }catch(RuntimeException e) {
193                 logger.error("Error while closing {}", persisterWithConfiguration.storage, e);
194                 if (lastException == null){
195                     lastException = e;
196                 } else {
197                     lastException.addSuppressed(e);
198                 }
199             }
200         }
201         if (lastException != null){
202             throw lastException;
203         }
204     }
205
206     @Override
207     public String toString() {
208         return "PersisterAggregator{" +
209                 "persisterWithConfigurations=" + persisterWithConfigurations +
210                 '}';
211     }
212 }