Acquire RFC8528 mount point map 49/84749/7
authorRobert Varga <robert.varga@pantheon.tech>
Thu, 26 Sep 2019 15:58:35 +0000 (17:58 +0200)
committerRobert Varga <nite@hq.sk>
Sat, 2 Nov 2019 07:41:11 +0000 (07:41 +0000)
This adds the logic to create a mountpoint map, without supporting
inline schemas and recursion.

Change-Id: I2a9306fd9e021f1b13b9bbac96cfe83e35c5285f
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
(cherry picked from commit 74f0f90b4fb0f38b4879a3cd521f9190cea86bf0)

netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/DeviceMountPointContext.java [new file with mode: 0644]
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDevice.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfMountPointContextFactory.java [new file with mode: 0644]

diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/DeviceMountPointContext.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/DeviceMountPointContext.java
new file mode 100644 (file)
index 0000000..a516d4d
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.connect.netconf;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Optional;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.concepts.Immutable;
+import org.opendaylight.yangtools.rfc8528.data.api.MountPointContext;
+import org.opendaylight.yangtools.rfc8528.data.api.MountPointContextFactory;
+import org.opendaylight.yangtools.rfc8528.data.api.MountPointIdentifier;
+import org.opendaylight.yangtools.rfc8528.model.api.SchemaMountConstants;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.util.AbstractSchemaContextProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+// TODO: this should really come from rfc8528-data-util
+final class DeviceMountPointContext extends AbstractSchemaContextProvider implements Immutable, MountPointContext {
+    private static final Logger LOG = LoggerFactory.getLogger(DeviceMountPointContext.class);
+    private static final NodeIdentifier MOUNT_POINT = NodeIdentifier.create(
+        QName.create(SchemaMountConstants.RFC8528_MODULE, "mount-point").intern());
+    private static final NodeIdentifier CONFIG = NodeIdentifier.create(
+        QName.create(SchemaMountConstants.RFC8528_MODULE, "config").intern());
+    private static final NodeIdentifier MODULE = NodeIdentifier.create(
+        QName.create(SchemaMountConstants.RFC8528_MODULE, "module").intern());
+    private static final NodeIdentifier LABEL = NodeIdentifier.create(
+        QName.create(SchemaMountConstants.RFC8528_MODULE, "label").intern());
+    private static final NodeIdentifier SCHEMA_REF = NodeIdentifier.create(
+        QName.create(SchemaMountConstants.RFC8528_MODULE, "schema-ref").intern());
+    private static final NodeIdentifier INLINE = NodeIdentifier.create(
+        QName.create(SchemaMountConstants.RFC8528_MODULE, "inline").intern());
+    private static final NodeIdentifier SHARED_SCHEMA = NodeIdentifier.create(
+        QName.create(SchemaMountConstants.RFC8528_MODULE, "shared-schema").intern());
+    private static final NodeIdentifier PARENT_REFERENCE = NodeIdentifier.create(
+        QName.create(SchemaMountConstants.RFC8528_MODULE, "parent-reference").intern());
+
+    private final ImmutableMap<MountPointIdentifier, NetconfMountPointContextFactory> mountPoints;
+
+    private DeviceMountPointContext(final SchemaContext schemaContext,
+            final Map<MountPointIdentifier, NetconfMountPointContextFactory> mountPoints) {
+        super(schemaContext);
+        this.mountPoints = ImmutableMap.copyOf(mountPoints);
+    }
+
+    static MountPointContext create(final MountPointContext emptyContext, final ContainerNode mountData) {
+        final Optional<DataContainerChild<?, ?>> optMountPoint = mountData.getChild(MOUNT_POINT);
+        if (!optMountPoint.isPresent()) {
+            LOG.debug("mount-point list not present in {}", mountData);
+            return emptyContext;
+        }
+
+        final SchemaContext schemaContext = emptyContext.getSchemaContext();
+        final DataContainerChild<?, ?> mountPoint = optMountPoint.get();
+        checkArgument(mountPoint instanceof MapNode, "mount-point list %s is not a MapNode", mountPoint);
+
+        final Map<MountPointIdentifier, NetconfMountPointContextFactory> mountPoints = new HashMap<>();
+        for (MapEntryNode entry : ((MapNode) mountPoint).getValue()) {
+            final String moduleName = entry.getChild(MODULE).map(mod -> {
+                checkArgument(mod instanceof LeafNode, "Unexpected module leaf %s", mod);
+                final Object value = mod.getValue();
+                checkArgument(value instanceof String, "Unexpected module leaf value %s", value);
+                return (String) value;
+            }).orElseThrow(() -> new IllegalArgumentException("Mount module missing in " + entry));
+            final Iterator<Module> it = schemaContext.findModules(moduleName).iterator();
+            checkArgument(it.hasNext(), "Failed to find a module named %s", moduleName);
+            final QNameModule module = it.next().getQNameModule();
+
+            final MountPointIdentifier mountId = MountPointIdentifier.of(QName.create(module,
+                entry.getChild(LABEL).map(lbl -> {
+                    checkArgument(lbl instanceof LeafNode, "Unexpected label leaf %s", lbl);
+                    final Object value = lbl.getValue();
+                    checkArgument(value instanceof String, "Unexpected label leaf value %s", value);
+                    return (String) value;
+                }).orElseThrow(() -> new IllegalArgumentException("Mount module missing in " + entry))));
+
+            final DataContainerChild<?, ?> child = entry.getChild(SCHEMA_REF).orElseThrow(
+                () -> new IllegalArgumentException("Missing schema-ref choice in " + entry));
+            checkArgument(child instanceof ChoiceNode, "Unexpected schema-ref choice %s", child);
+            final ChoiceNode schemaRef = (ChoiceNode) child;
+
+            final Optional<DataContainerChild<?, ?>> maybeShared = schemaRef.getChild(SHARED_SCHEMA);
+            if (!maybeShared.isPresent()) {
+                LOG.debug("Ignoring non-shared mountpoint entry {}", entry);
+                continue;
+            }
+
+            mountPoints.put(mountId, new NetconfMountPointContextFactory(schemaContext));
+        }
+
+        return new DeviceMountPointContext(schemaContext, mountPoints);
+    }
+
+    @Override
+    public Optional<MountPointContextFactory> findMountPoint(@NonNull final MountPointIdentifier label) {
+        return Optional.ofNullable(mountPoints.get(requireNonNull(label)));
+    }
+}
index 8a16ed177e4bad7ab76cc5968c3fee6f6020f64b..ef28d7252dc7d317c03120a542b21c2460683fc2 100644 (file)
@@ -9,6 +9,7 @@ package org.opendaylight.netconf.sal.connect.netconf;
 
 import static com.google.common.base.Preconditions.checkState;
 import static java.util.Objects.requireNonNull;
+import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_GET_NODEID;
 
 import com.google.common.base.Predicates;
 import com.google.common.collect.Collections2;
@@ -56,7 +57,14 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev15
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.unavailable.capabilities.UnavailableCapability;
 import org.opendaylight.yangtools.rcf8528.data.util.EmptyMountPointContext;
 import org.opendaylight.yangtools.rfc8528.data.api.MountPointContext;
+import org.opendaylight.yangtools.rfc8528.model.api.SchemaMountConstants;
 import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.RpcError;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException;
 import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactory;
@@ -81,6 +89,11 @@ public class NetconfDevice
             justification = "Needed for common logging of related classes")
     static final Logger LOG = LoggerFactory.getLogger(NetconfDevice.class);
 
+    private static final QName RFC8528_SCHEMA_MOUNTS_QNAME = QName.create(
+        SchemaMountConstants.RFC8528_MODULE, "schema-mounts").intern();
+    private static final YangInstanceIdentifier RFC8528_SCHEMA_MOUNTS = YangInstanceIdentifier.create(
+        NodeIdentifier.create(RFC8528_SCHEMA_MOUNTS_QNAME));
+
     protected final RemoteDeviceId id;
     protected final SchemaContextFactory schemaContextFactory;
     protected final SchemaSourceRegistry schemaRegistry;
@@ -154,17 +167,12 @@ public class NetconfDevice
         }
 
         // Set up the SchemaContext for the device
-        final ListenableFuture<SchemaContext> futureSchema = Futures.transformAsync(sourceResolverFuture, schemas -> {
-            LOG.debug("{}: Resolved device sources to {}", id, schemas);
-            addProvidedSourcesToSchemaRegistry(schemas);
-            return new SchemaSetup(schemas, remoteSessionCapabilities).startResolution();
-        }, processingExecutor);
+        final ListenableFuture<SchemaContext> futureSchema = Futures.transformAsync(sourceResolverFuture,
+            deviceSources -> assembleSchemaContext(deviceSources, remoteSessionCapabilities), processingExecutor);
 
         // Potentially acquire mount point list and interpret it
-        final ListenableFuture<MountPointContext> futureContext = Futures.transform(futureSchema, schemaContext -> {
-            // FIXME: check if there is RFC8528 schema available
-            return new EmptyMountPointContext(schemaContext);
-        }, processingExecutor);
+        final ListenableFuture<MountPointContext> futureContext = Futures.transformAsync(futureSchema,
+            schemaContext -> createMountPointContext(schemaContext, baseSchema, listener), processingExecutor);
 
         Futures.addCallback(futureContext, new FutureCallback<MountPointContext>() {
             @Override
@@ -280,13 +288,53 @@ public class NetconfDevice
         this.connected = connected;
     }
 
-    private void addProvidedSourcesToSchemaRegistry(final DeviceSources deviceSources) {
+    private ListenableFuture<SchemaContext> assembleSchemaContext(final DeviceSources deviceSources,
+            final NetconfSessionPreferences remoteSessionCapabilities) {
+        LOG.debug("{}: Resolved device sources to {}", id, deviceSources);
         final SchemaSourceProvider<YangTextSchemaSource> yangProvider = deviceSources.getSourceProvider();
         for (final SourceIdentifier sourceId : deviceSources.getProvidedSources()) {
             sourceRegistrations.add(schemaRegistry.registerSchemaSource(yangProvider,
-                    PotentialSchemaSource.create(
-                            sourceId, YangTextSchemaSource.class, PotentialSchemaSource.Costs.REMOTE_IO.getValue())));
+                PotentialSchemaSource.create(sourceId, YangTextSchemaSource.class,
+                    PotentialSchemaSource.Costs.REMOTE_IO.getValue())));
         }
+
+        return new SchemaSetup(deviceSources, remoteSessionCapabilities).startResolution();
+    }
+
+    private ListenableFuture<MountPointContext> createMountPointContext(final SchemaContext schemaContext,
+            final BaseSchema baseSchema, final NetconfDeviceCommunicator listener) {
+        final MountPointContext emptyContext = new EmptyMountPointContext(schemaContext);
+        if (!schemaContext.findModule(SchemaMountConstants.RFC8528_MODULE).isPresent()) {
+            return Futures.immediateFuture(emptyContext);
+        }
+
+        // Create a temporary RPC invoker and acquire the mount point tree
+        LOG.debug("{}: Acquiring available mount points", id);
+        final NetconfDeviceRpc deviceRpc = new NetconfDeviceRpc(schemaContext, listener,
+            new NetconfMessageTransformer(emptyContext, false, baseSchema));
+
+        return Futures.transform(deviceRpc.invokeRpc(NetconfMessageTransformUtil.NETCONF_GET_PATH,
+            Builders.containerBuilder().withNodeIdentifier(NETCONF_GET_NODEID)
+                .withChild(NetconfMessageTransformUtil.toFilterStructure(RFC8528_SCHEMA_MOUNTS, schemaContext))
+                .build()), rpcResult -> processSchemaMounts(rpcResult, emptyContext), MoreExecutors.directExecutor());
+    }
+
+    private MountPointContext processSchemaMounts(final DOMRpcResult rpcResult, final MountPointContext emptyContext) {
+        final Collection<? extends RpcError> errors = rpcResult.getErrors();
+        if (!errors.isEmpty()) {
+            LOG.warn("{}: Schema-mounts acquisition resulted in errors {}", id, errors);
+        }
+        final NormalizedNode<?, ?> schemaMounts = rpcResult.getResult();
+        if (schemaMounts == null) {
+            LOG.debug("{}: device does not define any schema mounts", id);
+            return emptyContext;
+        }
+        if (!(schemaMounts instanceof ContainerNode)) {
+            LOG.warn("{}: ignoring non-container schema mounts {}", id, schemaMounts);
+            return emptyContext;
+        }
+
+        return DeviceMountPointContext.create(emptyContext, (ContainerNode) schemaMounts);
     }
 
     @Override
diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfMountPointContextFactory.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfMountPointContextFactory.java
new file mode 100644 (file)
index 0000000..1d8686c
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.sal.connect.netconf;
+
+import java.util.Map;
+import org.opendaylight.yangtools.rcf8528.data.util.EmptyMountPointContext;
+import org.opendaylight.yangtools.rfc8528.data.api.MountPointChild;
+import org.opendaylight.yangtools.rfc8528.data.api.MountPointContext;
+import org.opendaylight.yangtools.rfc8528.data.api.MountPointContextFactory;
+import org.opendaylight.yangtools.rfc8528.data.api.YangLibraryConstants.ContainerName;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.parser.api.YangParserException;
+
+// TODO: this should really come from mdsal-yanglib-rfc8525
+final class NetconfMountPointContextFactory implements MountPointContextFactory {
+    private final MountPointContext mountPoint;
+
+    NetconfMountPointContextFactory(final SchemaContext schemaContext) {
+        mountPoint = new EmptyMountPointContext(schemaContext);
+    }
+
+    @Override
+    public MountPointContext createContext(final Map<ContainerName, MountPointChild> libraryContainers,
+            final MountPointChild schemaMounts) throws YangParserException {
+        return mountPoint;
+    }
+}