Extend clustered-app-config to read default data from XML file 02/41902/4
authorTom Pantelis <tpanteli@brocade.com>
Fri, 15 Jul 2016 15:28:48 +0000 (11:28 -0400)
committerTom Pantelis <tpanteli@brocade.com>
Wed, 20 Jul 2016 08:46:47 +0000 (04:46 -0400)
The default data can be specified in the clustered-app-config element
but it's also useful for scripting/automation or convenience to be able
to specify the default data in external XML file. The
clustered-app-config will now look for a file of the form

  <yang module name>_<container name>.xml

in a well-known location, etc/opendaylight/datastore/initial/config.

The XML file name can also be explicitly specified via the
"default-config-file-name" attribute.

Change-Id: Id310ef5ae121b8b9444a2102b93c3e382e421687
Signed-off-by: Tom Pantelis <tpanteli@brocade.com>
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/DataStoreAppConfigMetadata.java
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/OpendaylightNamespaceHandler.java
opendaylight/blueprint/src/main/resources/opendaylight-blueprint-ext-1.0.0.xsd

index f8a6127f0a29fd39a017966e1fdf541c455369bb..c83e4e83127356448331eaa1beba1c00d32853ef 100644 (file)
@@ -13,6 +13,8 @@ import com.google.common.base.Strings;
 import com.google.common.util.concurrent.CheckedFuture;
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
+import java.io.File;
+import java.io.FileInputStream;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -20,6 +22,7 @@ import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
+import javax.xml.parsers.DocumentBuilderFactory;
 import org.apache.aries.blueprint.services.ExtendedBlueprintContainer;
 import org.opendaylight.controller.md.sal.binding.api.ClusteredDataTreeChangeListener;
 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
@@ -52,6 +55,7 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.osgi.service.blueprint.container.ComponentDefinitionException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
 /**
@@ -67,9 +71,26 @@ public class DataStoreAppConfigMetadata extends AbstractDependentComponentFactor
 
     static final String BINDING_CLASS = "binding-class";
     static final String DEFAULT_CONFIG = "default-config";
+    static final String DEFAULT_CONFIG_FILE_NAME = "default-config-file-name";
     static final String LIST_KEY_VALUE = "list-key-value";
 
+    private static final String DEFAULT_APP_CONFIG_FILE_PATH = "etc" + File.separator +
+            "opendaylight" + File.separator + "datastore" + File.separator + "initial" +
+            File.separator + "config";
+
+    private static final DocumentBuilderFactory DOC_BUILDER_FACTORY;
+
+    static {
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        factory.setNamespaceAware(true);
+        factory.setCoalescing(true);
+        factory.setIgnoringElementContentWhitespace(true);
+        factory.setIgnoringComments(true);
+        DOC_BUILDER_FACTORY = factory;
+    }
+
     private final Element defaultAppConfigElement;
+    private final String defaultAppConfigFileName;
     private final String appConfigBindingClassName;
     private final String appConfigListKeyValue;
     private final AtomicBoolean readingInitialAppConfig = new AtomicBoolean(true);
@@ -84,9 +105,11 @@ public class DataStoreAppConfigMetadata extends AbstractDependentComponentFactor
     private volatile BindingNormalizedNodeSerializer bindingSerializer;
 
     public DataStoreAppConfigMetadata(@Nonnull String id, @Nonnull String appConfigBindingClassName,
-            @Nullable String appConfigListKeyValue, @Nullable Element defaultAppConfigElement) {
+            @Nullable String appConfigListKeyValue, @Nullable String defaultAppConfigFileName,
+            @Nullable Element defaultAppConfigElement) {
         super(id);
         this.defaultAppConfigElement = defaultAppConfigElement;
+        this.defaultAppConfigFileName = defaultAppConfigFileName;
         this.appConfigBindingClassName = appConfigBindingClassName;
         this.appConfigListKeyValue = appConfigListKeyValue;
     }
@@ -295,7 +318,11 @@ public class DataStoreAppConfigMetadata extends AbstractDependentComponentFactor
             return null;
         }
 
-        NormalizedNode<?, ?> dataNode = parsePossibleDefaultAppConfigElement(schemaContext, dataSchema);
+        NormalizedNode<?, ?> dataNode = parsePossibleDefaultAppConfigXMLFile(schemaContext, dataSchema);
+        if(dataNode == null) {
+            dataNode = parsePossibleDefaultAppConfigElement(schemaContext, dataSchema);
+        }
+
         if(dataNode == null) {
             dataNode = bindingContext.newDefaultNode(dataSchema);
         }
@@ -311,6 +338,59 @@ public class DataStoreAppConfigMetadata extends AbstractDependentComponentFactor
         return appConfig;
     }
 
+    private NormalizedNode<?, ?> parsePossibleDefaultAppConfigXMLFile(SchemaContext schemaContext,
+            DataSchemaNode dataSchema) {
+
+        String appConfigFileName = defaultAppConfigFileName;
+        if(Strings.isNullOrEmpty(appConfigFileName)) {
+            String moduleName = findYangModuleName(bindingContext.bindingQName, schemaContext);
+            if(moduleName == null) {
+                return null;
+            }
+
+            appConfigFileName = moduleName + "_" + bindingContext.bindingQName.getLocalName() + ".xml";
+        }
+
+        File appConfigFile = new File(DEFAULT_APP_CONFIG_FILE_PATH, appConfigFileName);
+
+        LOG.debug("{}: parsePossibleDefaultAppConfigXMLFile looking for file {}", logName(),
+                appConfigFile.getAbsolutePath());
+
+        if(!appConfigFile.exists()) {
+            return null;
+        }
+
+        LOG.debug("{}: Found file {}", logName(), appConfigFile.getAbsolutePath());
+
+        DomToNormalizedNodeParserFactory parserFactory = DomToNormalizedNodeParserFactory.getInstance(
+                XmlUtils.DEFAULT_XML_CODEC_PROVIDER, schemaContext);
+
+        try(FileInputStream fis = new FileInputStream(appConfigFile)) {
+            Document root = DOC_BUILDER_FACTORY.newDocumentBuilder().parse(fis);
+            NormalizedNode<?, ?> dataNode = bindingContext.parseDataElement(root.getDocumentElement(), dataSchema,
+                    parserFactory);
+
+            LOG.debug("{}: Parsed data node: {}", logName(), dataNode);
+
+            return dataNode;
+        } catch (Exception e) {
+            setFailureMessage(String.format("%s: Could not read/parse app config file %s", logName(), appConfigFile));
+        }
+
+        return null;
+    }
+
+    private String findYangModuleName(QName qname, SchemaContext schemaContext) {
+        for(Module m: schemaContext.getModules()) {
+            if(qname.getModule().equals(m.getQNameModule())) {
+                return m.getName();
+            }
+        }
+
+        setFailureMessage(String.format("%s: Could not find yang module for QName %s", logName(), qname));
+        return null;
+    }
+
     @Nullable
     private NormalizedNode<?, ?> parsePossibleDefaultAppConfigElement(SchemaContext schemaContext,
             DataSchemaNode dataSchema) {
index 14ca14b69dcb35de9a251ad89c55b693b59a9b16..e6ddd48234a7b5906e5eb108b846aec4c7860655 100644 (file)
@@ -349,7 +349,8 @@ public class OpendaylightNamespaceHandler implements NamespaceHandler {
 
         return new DataStoreAppConfigMetadata(getId(context, element), element.getAttribute(
                 DataStoreAppConfigMetadata.BINDING_CLASS), element.getAttribute(
-                        DataStoreAppConfigMetadata.LIST_KEY_VALUE), defaultAppConfigElement);
+                        DataStoreAppConfigMetadata.LIST_KEY_VALUE), element.getAttribute(
+                                DataStoreAppConfigMetadata.DEFAULT_CONFIG_FILE_NAME), defaultAppConfigElement);
     }
 
     private Metadata parseSpecificReferenceList(Element element, ParserContext context) {
index b3a7646a37416483e778ba506b4dba58fde23e81..c451e48995fe4086c900a33c8082a52985a8cc53 100644 (file)
@@ -39,6 +39,7 @@
     </xsd:sequence>
     <xsd:attribute name="binding-class" type="bp:Tclass" use="required"/>
     <xsd:attribute name="list-key-value" type="xsd:string" use="optional"/>
+    <xsd:attribute name="default-config-file-name" type="xsd:string" use="optional"/>
     <xsd:attribute name="id" type="xsd:ID" use="required"/>
   </xsd:complexType>
   <xsd:element name="clustered-app-config" type="TclusteredAppConfig"/>