Improve segmented journal actor metrics
[controller.git] / opendaylight / blueprint / src / main / java / org / opendaylight / controller / blueprint / ext / DataStoreAppConfigMetadata.java
1 /*
2  * Copyright (c) 2016 Brocade Communications 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 package org.opendaylight.controller.blueprint.ext;
9
10 import com.google.common.util.concurrent.FluentFuture;
11 import com.google.common.util.concurrent.FutureCallback;
12 import com.google.common.util.concurrent.MoreExecutors;
13 import java.io.File;
14 import java.io.IOException;
15 import java.net.URISyntaxException;
16 import java.util.Collection;
17 import java.util.Objects;
18 import java.util.Optional;
19 import java.util.concurrent.atomic.AtomicBoolean;
20 import javax.xml.stream.XMLStreamException;
21 import org.apache.aries.blueprint.services.ExtendedBlueprintContainer;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.opendaylight.controller.blueprint.ext.DataStoreAppConfigDefaultXMLReader.ConfigURLProvider;
25 import org.opendaylight.mdsal.binding.api.DataBroker;
26 import org.opendaylight.mdsal.binding.api.DataObjectModification;
27 import org.opendaylight.mdsal.binding.api.DataObjectModification.ModificationType;
28 import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
29 import org.opendaylight.mdsal.binding.api.DataTreeModification;
30 import org.opendaylight.mdsal.binding.api.ReadTransaction;
31 import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer;
32 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
33 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
34 import org.opendaylight.yangtools.concepts.Registration;
35 import org.opendaylight.yangtools.yang.binding.ChildOf;
36 import org.opendaylight.yangtools.yang.binding.DataObject;
37 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
38 import org.opendaylight.yangtools.yang.model.api.SchemaTreeInference;
39 import org.osgi.service.blueprint.container.ComponentDefinitionException;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42 import org.w3c.dom.Element;
43 import org.xml.sax.SAXException;
44
45 /**
46  * Factory metadata corresponding to the "clustered-app-config" element that obtains an application's
47  * config data from the data store and provides the binding DataObject instance to the Blueprint container
48  * as a bean. In addition registers a DataTreeChangeListener to restart the Blueprint container when the
49  * config data is changed.
50  *
51  * @author Thomas Pantelis
52  */
53 public class DataStoreAppConfigMetadata extends AbstractDependentComponentFactoryMetadata {
54     private static final Logger LOG = LoggerFactory.getLogger(DataStoreAppConfigMetadata.class);
55
56     static final String BINDING_CLASS = "binding-class";
57     static final String DEFAULT_CONFIG = "default-config";
58     static final String DEFAULT_CONFIG_FILE_NAME = "default-config-file-name";
59     static final String LIST_KEY_VALUE = "list-key-value";
60
61     private static final String DEFAULT_APP_CONFIG_FILE_PATH = "etc" + File.separator + "opendaylight" + File.separator
62             + "datastore" + File.separator + "initial" + File.separator + "config";
63
64     private final Element defaultAppConfigElement;
65     private final String defaultAppConfigFileName;
66     private final String appConfigBindingClassName;
67     private final String appConfigListKeyValue;
68     private final UpdateStrategy appConfigUpdateStrategy;
69     private final AtomicBoolean readingInitialAppConfig = new AtomicBoolean(true);
70
71     private volatile BindingContext bindingContext;
72     private volatile Registration appConfigChangeListenerReg;
73     private volatile DataObject currentAppConfig;
74
75     // Note: the BindingNormalizedNodeSerializer interface is annotated as deprecated because there's an
76     // equivalent interface in the mdsal project but the corresponding binding classes in the controller
77     // project are still used - conversion to the mdsal binding classes hasn't occurred yet.
78     private volatile BindingNormalizedNodeSerializer bindingSerializer;
79
80     public DataStoreAppConfigMetadata(final String id, final @NonNull String appConfigBindingClassName,
81             final @Nullable String appConfigListKeyValue, final @Nullable String defaultAppConfigFileName,
82             final @NonNull UpdateStrategy updateStrategyValue, final @Nullable Element defaultAppConfigElement) {
83         super(id);
84         this.defaultAppConfigElement = defaultAppConfigElement;
85         this.defaultAppConfigFileName = defaultAppConfigFileName;
86         this.appConfigBindingClassName = appConfigBindingClassName;
87         this.appConfigListKeyValue = appConfigListKeyValue;
88         appConfigUpdateStrategy = updateStrategyValue;
89     }
90
91     @Override
92     @SuppressWarnings("unchecked")
93     public void init(final ExtendedBlueprintContainer container) {
94         super.init(container);
95
96         Class<DataObject> appConfigBindingClass;
97         try {
98             Class<?> bindingClass = container.getBundleContext().getBundle().loadClass(appConfigBindingClassName);
99             if (!ChildOf.class.isAssignableFrom(bindingClass)) {
100                 throw new ComponentDefinitionException(String.format(
101                         "%s: Specified app config binding class %s does not extend %s",
102                         logName(), appConfigBindingClassName, ChildOf.class.getName()));
103             }
104
105             appConfigBindingClass = (Class<DataObject>) bindingClass;
106         } catch (final ClassNotFoundException e) {
107             throw new ComponentDefinitionException(String.format("%s: Error loading app config binding class %s",
108                     logName(), appConfigBindingClassName), e);
109         }
110
111         bindingContext = BindingContext.create(logName(), appConfigBindingClass, appConfigListKeyValue);
112     }
113
114     @Override
115     public Object create() throws ComponentDefinitionException {
116         LOG.debug("{}: In create - currentAppConfig: {}", logName(), currentAppConfig);
117
118         super.onCreate();
119
120         return currentAppConfig;
121     }
122
123     @Override
124     protected void startTracking() {
125         // First get the BindingNormalizedNodeSerializer OSGi service. This will be used to create a default
126         // instance of the app config binding class, if necessary.
127
128         retrieveService("binding-codec", BindingNormalizedNodeSerializer.class, service -> {
129             bindingSerializer = (BindingNormalizedNodeSerializer)service;
130             retrieveDataBrokerService();
131         });
132     }
133
134     private void retrieveDataBrokerService() {
135         LOG.debug("{}: In retrieveDataBrokerService", logName());
136         // Get the binding DataBroker OSGi service.
137         retrieveService("data-broker", DataBroker.class, service -> retrieveInitialAppConfig((DataBroker)service));
138     }
139
140     private void retrieveInitialAppConfig(final DataBroker dataBroker) {
141         LOG.debug("{}: Got DataBroker instance - reading app config {}", logName(), bindingContext.appConfigPath);
142
143         setDependencyDesc("Initial app config " + bindingContext.appConfigBindingClass.getSimpleName());
144
145         // We register a DTCL to get updates and also read the app config data from the data store. If the app config
146         // data is present then both the read and initial DTCN update will return it. If the the data isn't present, we
147         // will not get an initial DTCN update so the read will indicate the data is not present.
148         appConfigChangeListenerReg = dataBroker.registerTreeChangeListener(
149             DataTreeIdentifier.of(LogicalDatastoreType.CONFIGURATION, bindingContext.appConfigPath),
150             this::onAppConfigChanged);
151
152         readInitialAppConfig(dataBroker);
153     }
154
155     private void readInitialAppConfig(final DataBroker dataBroker) {
156         final FluentFuture<Optional<DataObject>> future;
157         try (ReadTransaction readOnlyTx = dataBroker.newReadOnlyTransaction()) {
158             future = readOnlyTx.read(LogicalDatastoreType.CONFIGURATION, bindingContext.appConfigPath);
159         }
160
161         future.addCallback(new FutureCallback<Optional<DataObject>>() {
162             @Override
163             public void onSuccess(final Optional<DataObject> possibleAppConfig) {
164                 LOG.debug("{}: Read of app config {} succeeded: {}", logName(), bindingContext
165                         .appConfigBindingClass.getName(), possibleAppConfig);
166
167                 setInitialAppConfig(possibleAppConfig);
168             }
169
170             @Override
171             public void onFailure(final Throwable failure) {
172                 // We may have gotten the app config via the data tree change listener so only retry if not.
173                 if (readingInitialAppConfig.get()) {
174                     LOG.warn("{}: Read of app config {} failed - retrying", logName(),
175                             bindingContext.appConfigBindingClass.getName(), failure);
176
177                     readInitialAppConfig(dataBroker);
178                 }
179             }
180         }, MoreExecutors.directExecutor());
181     }
182
183     private void onAppConfigChanged(final Collection<DataTreeModification<DataObject>> changes) {
184         for (DataTreeModification<DataObject> change: changes) {
185             DataObjectModification<DataObject> changeRoot = change.getRootNode();
186             ModificationType type = changeRoot.modificationType();
187
188             LOG.debug("{}: onAppConfigChanged: {}, {}", logName(), type, change.getRootPath());
189
190             if (type == ModificationType.SUBTREE_MODIFIED || type == ModificationType.WRITE) {
191                 DataObject newAppConfig = changeRoot.dataAfter();
192
193                 LOG.debug("New app config instance: {}, previous: {}", newAppConfig, currentAppConfig);
194
195                 if (!setInitialAppConfig(Optional.of(newAppConfig))
196                         && !Objects.equals(currentAppConfig, newAppConfig)) {
197                     LOG.debug("App config was updated");
198
199                     if (appConfigUpdateStrategy == UpdateStrategy.RELOAD) {
200                         restartContainer();
201                     }
202                 }
203             } else if (type == ModificationType.DELETE) {
204                 LOG.debug("App config was deleted");
205
206                 if (appConfigUpdateStrategy == UpdateStrategy.RELOAD) {
207                     restartContainer();
208                 }
209             }
210         }
211     }
212
213     private boolean setInitialAppConfig(final Optional<DataObject> possibleAppConfig) {
214         boolean result = readingInitialAppConfig.compareAndSet(true, false);
215         if (result) {
216             DataObject localAppConfig;
217             if (possibleAppConfig.isPresent()) {
218                 localAppConfig = possibleAppConfig.orElseThrow();
219             } else {
220                 // No app config data is present so create an empty instance via the bindingSerializer service.
221                 // This will also return default values for leafs that haven't been explicitly set.
222                 localAppConfig = createDefaultInstance();
223             }
224
225             LOG.debug("{}: Setting currentAppConfig instance: {}", logName(), localAppConfig);
226
227             // Now publish the app config instance to the volatile field and notify the callback to let the
228             // container know our dependency is now satisfied.
229             currentAppConfig = localAppConfig;
230             setSatisfied();
231         }
232
233         return result;
234     }
235
236     private DataObject createDefaultInstance() {
237         try {
238             ConfigURLProvider inputStreamProvider = appConfigFileName -> {
239                 File appConfigFile = new File(DEFAULT_APP_CONFIG_FILE_PATH, appConfigFileName);
240                 LOG.debug("{}: parsePossibleDefaultAppConfigXMLFile looking for file {}", logName(),
241                         appConfigFile.getAbsolutePath());
242
243                 if (!appConfigFile.exists()) {
244                     return Optional.empty();
245                 }
246
247                 LOG.debug("{}: Found file {}", logName(), appConfigFile.getAbsolutePath());
248
249                 return Optional.of(appConfigFile.toURI().toURL());
250             };
251
252             DataStoreAppConfigDefaultXMLReader<?> reader = new DataStoreAppConfigDefaultXMLReader<>(logName(),
253                     defaultAppConfigFileName, getOSGiService(DOMSchemaService.class), bindingSerializer, bindingContext,
254                     inputStreamProvider);
255             return reader.createDefaultInstance(dataSchema -> {
256                 // Fallback if file cannot be read, try XML from Config
257                 NormalizedNode dataNode = parsePossibleDefaultAppConfigElement(dataSchema);
258                 if (dataNode == null) {
259                     // or, as last resort, defaults from the model
260                     return bindingContext.newDefaultNode(dataSchema);
261                 } else {
262                     return dataNode;
263                 }
264             });
265
266         } catch (ConfigXMLReaderException | IOException | SAXException | XMLStreamException | URISyntaxException e) {
267             if (e.getCause() == null) {
268                 setFailureMessage(e.getMessage());
269             } else {
270                 setFailure(e.getMessage(), e);
271             }
272             return null;
273         }
274     }
275
276     private @Nullable NormalizedNode parsePossibleDefaultAppConfigElement(final SchemaTreeInference dataSchema)
277             throws URISyntaxException, IOException, SAXException, XMLStreamException {
278         if (defaultAppConfigElement == null) {
279             return null;
280         }
281
282         LOG.debug("{}: parsePossibleDefaultAppConfigElement for {}", logName(), bindingContext.bindingQName);
283
284         LOG.debug("{}: Got app config schema: {}", logName(), dataSchema);
285
286         NormalizedNode dataNode = bindingContext.parseDataElement(defaultAppConfigElement, dataSchema);
287
288         LOG.debug("{}: Parsed data node: {}", logName(), dataNode);
289
290         return dataNode;
291     }
292
293     @Override
294     public void destroy(final Object instance) {
295         super.destroy(instance);
296
297         if (appConfigChangeListenerReg != null) {
298             appConfigChangeListenerReg.close();
299             appConfigChangeListenerReg = null;
300         }
301     }
302
303 }