Wait for RPCService registered in RpcServiceMetadata
[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.base.Optional;
11 import com.google.common.base.Preconditions;
12 import com.google.common.base.Strings;
13 import com.google.common.util.concurrent.CheckedFuture;
14 import com.google.common.util.concurrent.FutureCallback;
15 import com.google.common.util.concurrent.Futures;
16 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.List;
19 import java.util.Objects;
20 import java.util.concurrent.atomic.AtomicBoolean;
21 import javax.annotation.Nonnull;
22 import javax.annotation.Nullable;
23 import org.apache.aries.blueprint.services.ExtendedBlueprintContainer;
24 import org.opendaylight.controller.blueprint.BlueprintContainerRestartService;
25 import org.opendaylight.controller.md.sal.binding.api.ClusteredDataTreeChangeListener;
26 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
27 import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
28 import org.opendaylight.controller.md.sal.binding.api.DataObjectModification.ModificationType;
29 import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
30 import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
31 import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
32 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
33 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
34 import org.opendaylight.controller.sal.core.api.model.SchemaService;
35 import org.opendaylight.yangtools.binding.data.codec.api.BindingNormalizedNodeSerializer;
36 import org.opendaylight.yangtools.concepts.ListenerRegistration;
37 import org.opendaylight.yangtools.yang.binding.DataObject;
38 import org.opendaylight.yangtools.yang.binding.Identifiable;
39 import org.opendaylight.yangtools.yang.binding.Identifier;
40 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
41 import org.opendaylight.yangtools.yang.binding.util.BindingReflections;
42 import org.opendaylight.yangtools.yang.common.QName;
43 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
44 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
45 import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlUtils;
46 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
47 import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory;
48 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
49 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
50 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
51 import org.opendaylight.yangtools.yang.model.api.Module;
52 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
53 import org.osgi.framework.ServiceReference;
54 import org.osgi.service.blueprint.container.ComponentDefinitionException;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
57 import org.w3c.dom.Element;
58
59 /**
60  * Factory metadata corresponding to the "clustered-app-config" element that obtains an application's
61  * config data from the data store and provides the binding DataObject instance to the Blueprint container
62  * as a bean. In addition registers a DataTreeChangeListener to restart the Blueprint container when the
63  * config data is changed.
64  *
65  * @author Thomas Pantelis
66  */
67 public class DataStoreAppConfigMetadata extends AbstractDependentComponentFactoryMetadata {
68     private static final Logger LOG = LoggerFactory.getLogger(DataStoreAppConfigMetadata.class);
69
70     static final String BINDING_CLASS = "binding-class";
71     static final String DEFAULT_CONFIG = "default-config";
72     static final String LIST_KEY_VALUE = "list-key-value";
73
74     private final Element defaultAppConfigElement;
75     private final String appConfigBindingClassName;
76     private final String appConfigListKeyValue;
77     private final AtomicBoolean readingInitialAppConfig = new AtomicBoolean(true);
78
79     private volatile BindingContext bindingContext;
80     private volatile ListenerRegistration<?> appConfigChangeListenerReg;
81     private volatile DataObject currentAppConfig;
82
83     // Note: the BindingNormalizedNodeSerializer interface is annotated as deprecated because there's an
84     // equivalent interface in the mdsal project but the corresponding binding classes in the controller
85     // project are still used - conversion to the mdsal binding classes hasn't occurred yet.
86     private volatile BindingNormalizedNodeSerializer bindingSerializer;
87
88     public DataStoreAppConfigMetadata(@Nonnull String id, @Nonnull String appConfigBindingClassName,
89             @Nullable String appConfigListKeyValue, @Nullable Element defaultAppConfigElement) {
90         super(id);
91         this.defaultAppConfigElement = defaultAppConfigElement;
92         this.appConfigBindingClassName = appConfigBindingClassName;
93         this.appConfigListKeyValue = appConfigListKeyValue;
94     }
95
96     @Override
97     @SuppressWarnings("unchecked")
98     public void init(ExtendedBlueprintContainer container) {
99         super.init(container);
100
101         Class<DataObject> appConfigBindingClass;
102         try {
103             Class<?> bindingClass = container.getBundleContext().getBundle().loadClass(appConfigBindingClassName);
104             if(!DataObject.class.isAssignableFrom(bindingClass)) {
105                 throw new ComponentDefinitionException(String.format(
106                         "%s: Specified app config binding class %s does not extend %s",
107                         logName(), appConfigBindingClassName, DataObject.class.getName()));
108             }
109
110             appConfigBindingClass = (Class<DataObject>) bindingClass;
111         } catch(ClassNotFoundException e) {
112             throw new ComponentDefinitionException(String.format("%s: Error loading app config binding class %s",
113                     logName(), appConfigBindingClassName), e);
114         }
115
116         if(Identifiable.class.isAssignableFrom(appConfigBindingClass)) {
117             // The binding class corresponds to a yang list.
118             if(Strings.isNullOrEmpty(appConfigListKeyValue)) {
119                 throw new ComponentDefinitionException(String.format(
120                         "%s: App config binding class %s represents a yang list therefore \"%s\" must be specified",
121                         logName(), appConfigBindingClassName, LIST_KEY_VALUE));
122             }
123
124             try {
125                 bindingContext = ListBindingContext.newInstance(appConfigBindingClass, appConfigListKeyValue);
126             } catch(Exception e) {
127                 throw new ComponentDefinitionException(String.format(
128                         "%s: Error initializing for app config list binding class %s",
129                         logName(), appConfigBindingClassName), e);
130             }
131
132         } else {
133             bindingContext = new ContainerBindingContext(appConfigBindingClass);
134         }
135     }
136
137     @Override
138     public Object create() throws ComponentDefinitionException {
139         LOG.debug("{}: In create - currentAppConfig: {}", logName(), currentAppConfig);
140
141         super.onCreate();
142
143         return currentAppConfig;
144     }
145
146     @Override
147     protected void startTracking() {
148         // First get the BindingNormalizedNodeSerializer OSGi service. This will be used to create a default
149         // instance of the app config binding class, if necessary.
150
151         retrieveService("binding-codec", BindingNormalizedNodeSerializer.class, service -> {
152             bindingSerializer = (BindingNormalizedNodeSerializer)service;
153             retrieveDataBrokerService();
154         });
155     }
156
157     private void retrieveDataBrokerService() {
158         LOG.debug("{}: In retrieveDataBrokerService", logName());
159
160         // Get the binding DataBroker OSGi service.
161
162         retrieveService("data-broker", DataBroker.class, service -> retrieveInitialAppConfig((DataBroker)service));
163     }
164
165     private void retrieveInitialAppConfig(DataBroker dataBroker) {
166         LOG.debug("{}: Got DataBroker instance - reading app config {}", logName(), bindingContext.appConfigPath);
167
168         setDependendencyDesc("Initial app config " + bindingContext.appConfigBindingClass.getSimpleName());
169
170         // We register a DTCL to get updates and also read the app config data from the data store. If
171         // the app config data is present then both the read and initial DTCN update will return it. If the
172         // the data isn't present, we won't get an initial DTCN update so the read will indicate the data
173         // isn't present.
174
175         DataTreeIdentifier<DataObject> dataTreeId = new DataTreeIdentifier<>(LogicalDatastoreType.CONFIGURATION,
176                 bindingContext.appConfigPath);
177         appConfigChangeListenerReg = dataBroker.registerDataTreeChangeListener(dataTreeId,
178                 new ClusteredDataTreeChangeListener<DataObject>() {
179                     @Override
180                     public void onDataTreeChanged(Collection<DataTreeModification<DataObject>> changes) {
181                         onAppConfigChanged(changes);
182                     }
183                 });
184
185         readInitialAppConfig(dataBroker);
186     }
187
188     private void readInitialAppConfig(final DataBroker dataBroker) {
189
190         final ReadOnlyTransaction readOnlyTx = dataBroker.newReadOnlyTransaction();
191         CheckedFuture<Optional<DataObject>, ReadFailedException> future = readOnlyTx.read(
192                 LogicalDatastoreType.CONFIGURATION, bindingContext.appConfigPath);
193         Futures.addCallback(future, new FutureCallback<Optional<DataObject>>() {
194             @Override
195             public void onSuccess(Optional<DataObject> possibleAppConfig) {
196                 LOG.debug("{}: Read of app config {} succeeded: {}", logName(), bindingContext.appConfigBindingClass.getName(),
197                         possibleAppConfig);
198
199                 readOnlyTx.close();
200                 setInitialAppConfig(possibleAppConfig);
201             }
202
203             @Override
204             public void onFailure(Throwable t) {
205                 readOnlyTx.close();
206
207                 // We may have gotten the app config via the data tree change listener so only retry if not.
208                 if(readingInitialAppConfig.get()) {
209                     LOG.warn("{}: Read of app config {} failed - retrying", logName(),
210                             bindingContext.appConfigBindingClass.getName(), t);
211
212                     readInitialAppConfig(dataBroker);
213                 }
214             }
215         });
216     }
217
218     private void onAppConfigChanged(Collection<DataTreeModification<DataObject>> changes) {
219         for(DataTreeModification<DataObject> change: changes) {
220             DataObjectModification<DataObject> changeRoot = change.getRootNode();
221             ModificationType type = changeRoot.getModificationType();
222
223             LOG.debug("{}: onAppConfigChanged: {}, {}", logName(), type, change.getRootPath());
224
225             if(type == ModificationType.SUBTREE_MODIFIED || type == ModificationType.WRITE) {
226                 DataObject newAppConfig = changeRoot.getDataAfter();
227
228                 LOG.debug("New app config instance: {}, previous: {}", newAppConfig, currentAppConfig);
229
230                 if(!setInitialAppConfig(Optional.of(newAppConfig)) &&
231                         !Objects.equals(currentAppConfig, newAppConfig)) {
232                     LOG.debug("App config was updated - scheduling container for restart");
233
234                     restartContainer();
235                 }
236             } else if(type == ModificationType.DELETE) {
237                 LOG.debug("App config was deleted - scheduling container for restart");
238
239                 restartContainer();
240             }
241         }
242     }
243
244     private boolean setInitialAppConfig(Optional<DataObject> possibleAppConfig) {
245         boolean result = readingInitialAppConfig.compareAndSet(true, false);
246         if(result) {
247             DataObject localAppConfig;
248             if(possibleAppConfig.isPresent()) {
249                 localAppConfig = possibleAppConfig.get();
250             } else {
251                 // No app config data is present so create an empty instance via the bindingSerializer service.
252                 // This will also return default values for leafs that haven't been explicitly set.
253                 localAppConfig = createDefaultInstance();
254             }
255
256             LOG.debug("{}: Setting currentAppConfig instance: {}", logName(), localAppConfig);
257
258             // Now publish the app config instance to the volatile field and notify the callback to let the
259             // container know our dependency is now satisfied.
260             currentAppConfig = localAppConfig;
261             setSatisfied();
262         }
263
264         return result;
265     }
266
267     private DataObject createDefaultInstance() {
268         YangInstanceIdentifier yangPath = bindingSerializer.toYangInstanceIdentifier(bindingContext.appConfigPath);
269
270         LOG.debug("{}: Creating app config instance from path {}, Qname: {}", logName(), yangPath, bindingContext.bindingQName);
271
272         SchemaService schemaService = getOSGiService(SchemaService.class);
273         if(schemaService == null) {
274             setFailureMessage(String.format("%s: Could not obtain the SchemaService OSGi service", logName()));
275             return null;
276         }
277
278         SchemaContext schemaContext = schemaService.getGlobalContext();
279
280         Module module = schemaContext.findModuleByNamespaceAndRevision(bindingContext.bindingQName.getNamespace(),
281                 bindingContext.bindingQName.getRevision());
282         if(module == null) {
283             setFailureMessage(String.format("%s: Could not obtain the module schema for namespace %s, revision %s",
284                     logName(), bindingContext.bindingQName.getNamespace(), bindingContext.bindingQName.getRevision()));
285             return null;
286         }
287
288         DataSchemaNode dataSchema = module.getDataChildByName(bindingContext.bindingQName);
289         if(dataSchema == null) {
290             setFailureMessage(String.format("%s: Could not obtain the schema for %s", logName(), bindingContext.bindingQName));
291             return null;
292         }
293
294         if(!bindingContext.schemaType.isAssignableFrom(dataSchema.getClass())) {
295             setFailureMessage(String.format("%s: Expected schema type %s for %s but actual type is %s", logName(),
296                     bindingContext.schemaType, bindingContext.bindingQName, dataSchema.getClass()));
297             return null;
298         }
299
300         NormalizedNode<?, ?> dataNode = parsePossibleDefaultAppConfigElement(schemaContext, dataSchema);
301         if(dataNode == null) {
302             dataNode = bindingContext.newDefaultNode(dataSchema);
303         }
304
305         DataObject appConfig = bindingSerializer.fromNormalizedNode(yangPath, dataNode).getValue();
306
307         if(appConfig == null) {
308             // This shouldn't happen but need to handle it in case...
309             setFailureMessage(String.format("%s: Could not create instance for app config binding %s",
310                     logName(), bindingContext.appConfigBindingClass));
311         }
312
313         return appConfig;
314     }
315
316     @Nullable
317     private NormalizedNode<?, ?> parsePossibleDefaultAppConfigElement(SchemaContext schemaContext,
318             DataSchemaNode dataSchema) {
319         if(defaultAppConfigElement == null) {
320             return null;
321         }
322
323         LOG.debug("{}: parsePossibleDefaultAppConfigElement for {}", logName(), bindingContext.bindingQName);
324
325         DomToNormalizedNodeParserFactory parserFactory = DomToNormalizedNodeParserFactory.getInstance(
326                 XmlUtils.DEFAULT_XML_CODEC_PROVIDER, schemaContext);
327
328
329         LOG.debug("{}: Got app config schema: {}", logName(), dataSchema);
330
331         NormalizedNode<?, ?> dataNode = bindingContext.parseDataElement(defaultAppConfigElement, dataSchema,
332                 parserFactory);
333
334         LOG.debug("{}: Parsed data node: {}", logName(), dataNode);
335
336         return dataNode;
337     }
338
339     private void restartContainer() {
340         BlueprintContainerRestartService restartService = getOSGiService(BlueprintContainerRestartService.class);
341         if(restartService != null) {
342             restartService.restartContainerAndDependents(container().getBundleContext().getBundle());
343         }
344     }
345
346     @SuppressWarnings("unchecked")
347     @Nullable
348     private <T> T getOSGiService(Class<T> serviceInterface) {
349         try {
350             ServiceReference<T> serviceReference =
351                     container().getBundleContext().getServiceReference(serviceInterface);
352             if(serviceReference == null) {
353                 LOG.warn("{}: {} reference not found", logName(), serviceInterface.getSimpleName());
354                 return null;
355             }
356
357             T service = (T)container().getService(serviceReference);
358             if(service == null) {
359                 // This could happen on shutdown if the service was already unregistered so we log as debug.
360                 LOG.debug("{}: {} was not found", logName(), serviceInterface.getSimpleName());
361             }
362
363             return service;
364         } catch(IllegalStateException e) {
365             // This is thrown if the BundleContext is no longer valid which is possible on shutdown so we
366             // log as debug.
367             LOG.debug("{}: Error obtaining {}", logName(), serviceInterface.getSimpleName(), e);
368         }
369
370         return null;
371     }
372
373     @Override
374     public void destroy(Object instance) {
375         super.destroy(instance);
376
377         if(appConfigChangeListenerReg != null) {
378             appConfigChangeListenerReg.close();
379             appConfigChangeListenerReg = null;
380         }
381     }
382
383     /**
384      * Internal base class to abstract binding type-specific behavior.
385      */
386     private static abstract class BindingContext {
387         final InstanceIdentifier<DataObject> appConfigPath;
388         final Class<DataObject> appConfigBindingClass;
389         final Class<? extends DataSchemaNode> schemaType;
390         final QName bindingQName;
391
392         protected BindingContext(Class<DataObject> appConfigBindingClass, InstanceIdentifier<DataObject> appConfigPath,
393                 Class<? extends DataSchemaNode> schemaType) {
394             this.appConfigBindingClass = appConfigBindingClass;
395             this.appConfigPath = appConfigPath;
396             this.schemaType = schemaType;
397
398             bindingQName = BindingReflections.findQName(appConfigBindingClass);
399         }
400
401         abstract NormalizedNode<?, ?> parseDataElement(Element element, DataSchemaNode dataSchema,
402                 DomToNormalizedNodeParserFactory parserFactory);
403
404         abstract NormalizedNode<?, ?> newDefaultNode(DataSchemaNode dataSchema);
405     }
406
407     /**
408      * BindingContext implementation for a container binding.
409      */
410     private static class ContainerBindingContext extends BindingContext {
411         ContainerBindingContext(Class<DataObject> appConfigBindingClass) {
412             super(appConfigBindingClass, InstanceIdentifier.create(appConfigBindingClass), ContainerSchemaNode.class);
413         }
414
415         @Override
416         NormalizedNode<?, ?> newDefaultNode(DataSchemaNode dataSchema) {
417             return ImmutableNodes.containerNode(bindingQName);
418         }
419
420         @Override
421         NormalizedNode<?, ?> parseDataElement(Element element, DataSchemaNode dataSchema,
422                 DomToNormalizedNodeParserFactory parserFactory) {
423             return parserFactory.getContainerNodeParser().parse(Collections.singletonList(element),
424                     (ContainerSchemaNode)dataSchema);
425         }
426     }
427
428     /**
429      * BindingContext implementation for a list binding.
430      */
431     private static class ListBindingContext extends BindingContext {
432         final String appConfigListKeyValue;
433
434         ListBindingContext(Class<DataObject> appConfigBindingClass, InstanceIdentifier<DataObject> appConfigPath,
435                 String appConfigListKeyValue) {
436             super(appConfigBindingClass, appConfigPath, ListSchemaNode.class);
437             this.appConfigListKeyValue = appConfigListKeyValue;
438         }
439
440         @SuppressWarnings({ "rawtypes", "unchecked" })
441         private static ListBindingContext newInstance(Class<DataObject> bindingClass, String listKeyValue)
442                 throws Exception {
443             // We assume the yang list key type is string.
444             Identifier keyInstance = (Identifier) bindingClass.getMethod("getKey").getReturnType().
445                     getConstructor(String.class).newInstance(listKeyValue);
446             InstanceIdentifier appConfigPath = InstanceIdentifier.builder((Class)bindingClass, keyInstance).build();
447             return new ListBindingContext(bindingClass, appConfigPath, listKeyValue);
448         }
449
450         @Override
451         NormalizedNode<?, ?> newDefaultNode(DataSchemaNode dataSchema) {
452             // We assume there's only one key for the list.
453             List<QName> keys = ((ListSchemaNode)dataSchema).getKeyDefinition();
454             Preconditions.checkArgument(keys.size() == 1, "Expected only 1 key for list %s", appConfigBindingClass);
455             QName listKeyQName = keys.iterator().next();
456             return ImmutableNodes.mapEntryBuilder(bindingQName, listKeyQName, appConfigListKeyValue).build();
457         }
458
459         @Override
460         NormalizedNode<?, ?> parseDataElement(Element element, DataSchemaNode dataSchema,
461                 DomToNormalizedNodeParserFactory parserFactory) {
462             return parserFactory.getMapEntryNodeParser().parse(Collections.singletonList(element),
463                     (ListSchemaNode)dataSchema);
464         }
465     }
466 }