Merge "Update config-module-archetype."
[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 org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder;
13 import org.opendaylight.controller.config.persist.api.Persister;
14 import org.opendaylight.controller.config.persist.api.StorageAdapter;
15 import org.opendaylight.controller.netconf.persist.impl.osgi.ConfigPersisterActivator;
16 import org.opendaylight.controller.netconf.persist.impl.osgi.PropertiesProviderBaseImpl;
17 import org.slf4j.Logger;
18 import org.slf4j.LoggerFactory;
19
20 import java.io.IOException;
21 import java.util.ArrayList;
22 import java.util.Collections;
23 import java.util.List;
24 import java.util.ListIterator;
25
26 /**
27  * {@link Persister} implementation that delegates persisting functionality to
28  * underlying {@link Persister} storages. Each storage has unique id, class, readonly value.
29  *
30  * Storage adapters are low level persisters that do the heavy lifting for this
31  * class. Instances of storage adapters can be injected directly via constructor
32  * or instantiated from a full name of its class provided in a properties file.
33  *
34  * Example configuration:<pre>
35  netconf.config.persister.active=2,3
36  # read startup configuration
37  netconf.config.persister.1.storageAdapterClass=org.opendaylight.controller.config.persist.storage.directory.xml.XmlDirectoryStorageAdapter
38  netconf.config.persister.1.properties.fileStorage=configuration/initial/
39
40  netconf.config.persister.2.storageAdapterClass=org.opendaylight.controller.config.persist.storage.file.xml.XmlFileStorageAdapter
41  netconf.config.persister.2.readonly=true
42  netconf.config.persister.2.properties.fileStorage=configuration/current/controller.config.1.xml
43
44  netconf.config.persister.3.storageAdapterClass=org.opendaylight.controller.config.persist.storage.file.xml.XmlFileStorageAdapter
45  netconf.config.persister.3.properties.fileStorage=configuration/current/controller.config.2.xml
46  netconf.config.persister.3.properties.numberOfBackups=3
47
48  </pre>
49  * During server startup {@link ConfigPersisterNotificationHandler} requests last snapshot from underlying storages.
50  * Each storage can respond by giving snapshot or absent response.
51  * The {@link #loadLastConfigs()} will search for first non-absent response from storages ordered backwards as user
52  * specified (first '3', then '2').
53  *
54  * When a commit notification is received, '2' will be omitted because readonly flag is set to true, so
55  * only '3' will have a chance to persist new configuration. If readonly was false or not specified, both storage adapters
56  * would be called in order specified by 'netconf.config.persister' property.
57  *
58  */
59 public final class PersisterAggregator implements Persister {
60     private static final Logger logger = LoggerFactory.getLogger(PersisterAggregator.class);
61
62     public static class PersisterWithConfiguration {
63
64         private final Persister storage;
65         private final boolean readOnly;
66
67         public PersisterWithConfiguration(Persister storage, boolean readOnly) {
68             this.storage = storage;
69             this.readOnly = readOnly;
70         }
71
72         @VisibleForTesting
73         public Persister getStorage() {
74             return storage;
75         }
76
77         @VisibleForTesting
78         public boolean isReadOnly() {
79             return readOnly;
80         }
81
82         @Override
83         public String toString() {
84             return "PersisterWithConfiguration{" +
85                     "storage=" + storage +
86                     ", readOnly=" + readOnly +
87                     '}';
88         }
89     }
90
91     private static PersisterWithConfiguration loadConfiguration(final String index, final PropertiesProviderBaseImpl propertiesProvider) {
92
93         String classKey = index + "." + ConfigPersisterActivator.STORAGE_ADAPTER_CLASS_PROP_SUFFIX;
94         String storageAdapterClass = propertiesProvider.getProperty(classKey);
95         StorageAdapter storageAdapter;
96         if (storageAdapterClass == null || storageAdapterClass.equals("")) {
97             throw new IllegalStateException("No persister is defined in " +
98                     propertiesProvider.getFullKeyForReporting(classKey)
99                     + " property. Persister is not operational");
100         }
101
102         try {
103             Class<?> clazz = Class.forName(storageAdapterClass);
104             boolean implementsCorrectIfc = StorageAdapter.class.isAssignableFrom(clazz);
105             if (implementsCorrectIfc == false) {
106                 throw new IllegalArgumentException("Storage adapter " + clazz + " does not implement " + StorageAdapter.class);
107             }
108             storageAdapter = StorageAdapter.class.cast(clazz.newInstance());
109
110             boolean readOnly = false;
111             String readOnlyProperty = propertiesProvider.getProperty(index + "." + "readonly");
112             if (readOnlyProperty != null && readOnlyProperty.equals("true")) {
113                 readOnly = true;
114             }
115
116             PropertiesProviderAdapterImpl innerProvider = new PropertiesProviderAdapterImpl(propertiesProvider, index);
117             Persister storage = storageAdapter.instantiate(innerProvider);
118             return new PersisterWithConfiguration(storage, readOnly);
119         } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
120             throw new IllegalArgumentException("Unable to instantiate storage adapter from " + storageAdapterClass, e);
121         }
122     }
123
124     /**
125      * Persisters ordered by 'netconf.config.persister' property.
126      */
127     private final List<PersisterWithConfiguration> persisterWithConfigurations;
128
129     public PersisterAggregator(List<PersisterWithConfiguration> persisterWithConfigurations) {
130         this.persisterWithConfigurations = persisterWithConfigurations;
131
132     }
133
134     public static PersisterAggregator createFromProperties(PropertiesProviderBaseImpl propertiesProvider) {
135         List<PersisterWithConfiguration> persisterWithConfigurations = new ArrayList<>();
136         String prefixes = propertiesProvider.getProperty("active");
137         if (prefixes!=null && prefixes.isEmpty() == false) {
138             String [] keys = prefixes.split(",");
139             for (String index: keys) {
140                 persisterWithConfigurations.add(PersisterAggregator.loadConfiguration(index, propertiesProvider));
141             }
142         }
143         logger.debug("Initialized persister with following adapters {}", persisterWithConfigurations);
144         return new PersisterAggregator(persisterWithConfigurations);
145     }
146
147     @Override
148     public void persistConfig(ConfigSnapshotHolder holder) throws IOException {
149         for (PersisterWithConfiguration persisterWithConfiguration: persisterWithConfigurations){
150             if (!persisterWithConfiguration.readOnly){
151                 logger.debug("Calling {}.persistConfig",persisterWithConfiguration.storage);
152                 persisterWithConfiguration.storage.persistConfig(holder);
153             }
154         }
155     }
156
157     /**
158      * @return last non-empty result from input persisters
159      */
160     @Override
161     public List<ConfigSnapshotHolder> loadLastConfigs()  {
162         // iterate in reverse order
163         ListIterator<PersisterWithConfiguration> li = persisterWithConfigurations.listIterator(persisterWithConfigurations.size());
164         while(li.hasPrevious()) {
165             PersisterWithConfiguration persisterWithConfiguration = li.previous();
166             List<ConfigSnapshotHolder> configs = null;
167             try {
168                 configs = persisterWithConfiguration.storage.loadLastConfigs();
169             } catch (IOException e) {
170                 throw new RuntimeException("Error while calling loadLastConfig on " +  persisterWithConfiguration, e);
171             }
172             if (configs.isEmpty() == false) {
173                 logger.debug("Found non empty configs using {}:{}", persisterWithConfiguration, configs);
174                 return configs;
175             }
176         }
177         // no storage had an answer
178         logger.debug("No non-empty list of configuration snapshots found");
179         return Collections.emptyList();
180     }
181
182     @VisibleForTesting
183     List<PersisterWithConfiguration> getPersisterWithConfigurations() {
184         return persisterWithConfigurations;
185     }
186
187     @Override
188     public void close() {
189         RuntimeException lastException = null;
190         for (PersisterWithConfiguration persisterWithConfiguration: persisterWithConfigurations){
191             try{
192                 persisterWithConfiguration.storage.close();
193             }catch(RuntimeException e) {
194                 logger.error("Error while closing {}", persisterWithConfiguration.storage, e);
195                 if (lastException == null){
196                     lastException = e;
197                 } else {
198                     lastException.addSuppressed(e);
199                 }
200             }
201         }
202         if (lastException != null){
203             throw lastException;
204         }
205     }
206
207     @Override
208     public String toString() {
209         return "PersisterAggregator{" +
210                 "persisterWithConfigurations=" + persisterWithConfigurations +
211                 '}';
212     }
213 }