BUG-2635 Netconf monitoring for md-sal netconf northbound
[controller.git] / opendaylight / netconf / netconf-impl / src / main / java / org / opendaylight / controller / netconf / impl / osgi / NetconfMonitoringServiceImpl.java
index a7560fadb602cc450f84c71e2f9936cb131cd495..b02137b748c71584841df04817a9f8bb552147e2 100644 (file)
@@ -8,19 +8,32 @@
 package org.opendaylight.controller.netconf.impl.osgi;
 
 import com.google.common.base.Function;
+import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import io.netty.util.internal.ConcurrentSet;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import javax.annotation.Nonnull;
+import org.opendaylight.controller.netconf.api.Capability;
 import org.opendaylight.controller.netconf.api.monitoring.NetconfManagementSession;
 import org.opendaylight.controller.netconf.api.monitoring.NetconfMonitoringService;
-import org.opendaylight.controller.netconf.mapping.api.Capability;
-import org.opendaylight.controller.netconf.mapping.api.NetconfOperationProvider;
-import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService;
-import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceSnapshot;
+import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Uri;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.NetconfState;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.NetconfStateBuilder;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.Yang;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Capabilities;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.CapabilitiesBuilder;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Schemas;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.SchemasBuilder;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Sessions;
@@ -32,113 +45,208 @@ import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.mon
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.annotation.Nullable;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
-public class NetconfMonitoringServiceImpl implements NetconfMonitoringService, SessionMonitoringService {
+public class NetconfMonitoringServiceImpl implements NetconfMonitoringService, AutoCloseable {
 
-    private static final Logger logger = LoggerFactory.getLogger(NetconfMonitoringServiceImpl.class);
+    private static final Schema.Location NETCONF_LOCATION = new Schema.Location(Schema.Location.Enumeration.NETCONF);
+    private static final List<Schema.Location> NETCONF_LOCATIONS = ImmutableList.of(NETCONF_LOCATION);
+    private static final Logger LOG = LoggerFactory.getLogger(NetconfMonitoringServiceImpl.class);
+    private static final Function<NetconfManagementSession, Session> SESSION_FUNCTION = new Function<NetconfManagementSession, Session>() {
+        @Override
+        public Session apply(@Nonnull final NetconfManagementSession input) {
+            return input.toManagementSession();
+        }
+    };
+    private static final Function<Capability, Uri> CAPABILITY_TO_URI = new Function<Capability, Uri>() {
+        @Override
+        public Uri apply(final Capability input) {
+            return new Uri(input.getCapabilityUri());
+        }
+    };
 
     private final Set<NetconfManagementSession> sessions = new ConcurrentSet<>();
-    private final NetconfOperationProvider netconfOperationProvider;
+    private final NetconfOperationServiceFactory netconfOperationProvider;
+    private final Map<Uri, Capability> capabilities = new ConcurrentHashMap<>();
+
+    private final Set<MonitoringListener> listeners = Sets.newHashSet();
 
-    public NetconfMonitoringServiceImpl(NetconfOperationProvider netconfOperationProvider) {
+    public NetconfMonitoringServiceImpl(final NetconfOperationServiceFactory netconfOperationProvider) {
         this.netconfOperationProvider = netconfOperationProvider;
+        netconfOperationProvider.registerCapabilityListener(this);
     }
 
     @Override
-    public void onSessionUp(NetconfManagementSession session) {
-        logger.debug("Session {} up", session);
-        Preconditions.checkState(sessions.contains(session) == false, "Session %s was already added", session);
+    public synchronized void onSessionUp(final NetconfManagementSession session) {
+        LOG.debug("Session {} up", session);
+        Preconditions.checkState(!sessions.contains(session), "Session %s was already added", session);
         sessions.add(session);
+        notifyListeners();
     }
 
     @Override
-    public void onSessionDown(NetconfManagementSession session) {
-        logger.debug("Session {} down", session);
+    public synchronized void onSessionDown(final NetconfManagementSession session) {
+        LOG.debug("Session {} down", session);
         Preconditions.checkState(sessions.contains(session), "Session %s not present", session);
         sessions.remove(session);
+        notifyListeners();
     }
 
     @Override
-    public Sessions getSessions() {
-        return new SessionsBuilder().setSession(transformSessions(sessions)).build();
+    public synchronized Sessions getSessions() {
+        return new SessionsBuilder().setSession(ImmutableList.copyOf(Collections2.transform(sessions, SESSION_FUNCTION))).build();
     }
 
     @Override
-    public Schemas getSchemas() {
-        // capabilities should be split from operations (it will allow to move getSchema operation to monitoring module)
-        try (NetconfOperationServiceSnapshot snapshot = netconfOperationProvider.openSnapshot("netconf-monitoring")) {
-            return transformSchemas(snapshot.getServices());
-        } catch (RuntimeException e) {
+    public synchronized Schemas getSchemas() {
+        try {
+            return transformSchemas(netconfOperationProvider.getCapabilities());
+        } catch (final RuntimeException e) {
             throw e;
-        } catch (Exception e) {
+        } catch (final Exception e) {
             throw new IllegalStateException("Exception while closing", e);
         }
     }
 
-    private Schemas transformSchemas(Set<NetconfOperationService> services) {
-        Set<Capability> caps = Sets.newHashSet();
+    @Override
+    public synchronized String getSchemaForCapability(final String moduleName, final Optional<String> revision) {
 
-        List<Schema> schemas = Lists.newArrayList();
+        // FIXME not effective at all
 
+        Map<String, Map<String, String>> mappedModulesToRevisionToSchema = Maps.newHashMap();
 
-        for (NetconfOperationService netconfOperationService : services) {
-            // TODO check for duplicates ? move capability merging to snapshot
-            // Split capabilities from operations first and delete this duplicate code
-            caps.addAll(netconfOperationService.getCapabilities());
-        }
+        final Collection<Capability> caps = capabilities.values();
 
         for (Capability cap : caps) {
-            SchemaBuilder builder = new SchemaBuilder();
-
-            if (cap.getCapabilitySchema().isPresent() == false) {
+            if (!cap.getModuleName().isPresent()
+                    || !cap.getRevision().isPresent()
+                    || !cap.getCapabilitySchema().isPresent()){
                 continue;
             }
 
-            Preconditions.checkState(cap.getModuleNamespace().isPresent());
-            builder.setNamespace(new Uri(cap.getModuleNamespace().get()));
+            final String currentModuleName = cap.getModuleName().get();
+            Map<String, String> revisionMap = mappedModulesToRevisionToSchema.get(currentModuleName);
+            if (revisionMap == null) {
+                revisionMap = Maps.newHashMap();
+                mappedModulesToRevisionToSchema.put(currentModuleName, revisionMap);
+            }
+
+            String currentRevision = cap.getRevision().get();
+            revisionMap.put(currentRevision, cap.getCapabilitySchema().get());
+        }
+
+        Map<String, String> revisionMapRequest = mappedModulesToRevisionToSchema.get(moduleName);
+        Preconditions.checkState(revisionMapRequest != null, "Capability for module %s not present, " + ""
+                + "available modules : %s", moduleName, Collections2.transform(caps, CAPABILITY_TO_URI));
 
-            Preconditions.checkState(cap.getRevision().isPresent());
-            String version = cap.getRevision().get();
-            builder.setVersion(version);
+        if (revision.isPresent()) {
+            String schema = revisionMapRequest.get(revision.get());
 
-            Preconditions.checkState(cap.getModuleName().isPresent());
-            String identifier = cap.getModuleName().get();
-            builder.setIdentifier(identifier);
+            Preconditions.checkState(schema != null,
+                    "Capability for module %s:%s not present, available revisions for module: %s", moduleName,
+                    revision.get(), revisionMapRequest.keySet());
 
-            builder.setFormat(Yang.class);
+            return schema;
+        } else {
+            Preconditions.checkState(revisionMapRequest.size() == 1,
+                    "Expected 1 capability for module %s, available revisions : %s", moduleName,
+                    revisionMapRequest.keySet());
+            return revisionMapRequest.values().iterator().next();
+        }
+    }
 
-            builder.setLocation(transformLocations(cap.getLocation().or(Collections.<String>emptyList())));
+    @Override
+    public synchronized Capabilities getCapabilities() {
+        return new CapabilitiesBuilder().setCapability(Lists.newArrayList(capabilities.keySet())).build();
+    }
 
-            builder.setKey(new SchemaKey(Yang.class, identifier, version));
+    @Override
+    public synchronized AutoCloseable registerListener(final MonitoringListener listener) {
+        listeners.add(listener);
+        listener.onStateChanged(getCurrentNetconfState());
+        return new AutoCloseable() {
+            @Override
+            public void close() throws Exception {
+                listeners.remove(listener);
+            }
+        };
+    }
 
-            schemas.add(builder.build());
+    private NetconfState getCurrentNetconfState() {
+        return new NetconfStateBuilder()
+                .setCapabilities(getCapabilities())
+                .setSchemas(getSchemas())
+                .setSessions(getSessions())
+                .build();
+    }
+
+    private static Schemas transformSchemas(final Set<Capability> caps) {
+        final List<Schema> schemas = new ArrayList<>(caps.size());
+        for (final Capability cap : caps) {
+            if (cap.getCapabilitySchema().isPresent()) {
+                final SchemaBuilder builder = new SchemaBuilder();
+                Preconditions.checkState(cap.getModuleNamespace().isPresent());
+                builder.setNamespace(new Uri(cap.getModuleNamespace().get()));
+
+                Preconditions.checkState(cap.getRevision().isPresent());
+                final String version = cap.getRevision().get();
+                builder.setVersion(version);
+
+                Preconditions.checkState(cap.getModuleName().isPresent());
+                final String identifier = cap.getModuleName().get();
+                builder.setIdentifier(identifier);
+
+                builder.setFormat(Yang.class);
+
+                builder.setLocation(transformLocations(cap.getLocation()));
+
+                builder.setKey(new SchemaKey(Yang.class, identifier, version));
+
+                schemas.add(builder.build());
+            }
         }
 
         return new SchemasBuilder().setSchema(schemas).build();
     }
 
-    private List<Schema.Location> transformLocations(List<String> locations) {
-        List<Schema.Location> monitoringLocations = Lists.newArrayList();
-        monitoringLocations.add(new Schema.Location(Schema.Location.Enumeration.NETCONF));
+    private static List<Schema.Location> transformLocations(final Collection<String> locations) {
+        if (locations.isEmpty()) {
+            return NETCONF_LOCATIONS;
+        }
+
+        final Builder<Schema.Location> b = ImmutableList.builder();
+        b.add(NETCONF_LOCATION);
 
-        for (String location : locations) {
-            monitoringLocations.add(new Schema.Location(new Uri(location)));
+        for (final String location : locations) {
+            b.add(new Schema.Location(new Uri(location)));
         }
 
-        return monitoringLocations;
+        return b.build();
     }
 
-    private List<Session> transformSessions(Set<NetconfManagementSession> sessions) {
-        return Lists.newArrayList(Collections2.transform(sessions, new Function<NetconfManagementSession, Session>() {
-            @Nullable
-            @Override
-            public Session apply(@Nullable NetconfManagementSession input) {
-                return input.toManagementSession();
-            }
-        }));
+    @Override
+    public synchronized void onCapabilitiesAdded(final Set<Capability> addedCaps) {
+        // FIXME howto check for duplicates
+        this.capabilities.putAll(Maps.uniqueIndex(addedCaps, CAPABILITY_TO_URI));
+        notifyListeners();
+    }
+
+    private void notifyListeners() {
+        for (final MonitoringListener listener : listeners) {
+            listener.onStateChanged(getCurrentNetconfState());
+        }
+    }
+
+    @Override
+    public synchronized void onCapabilitiesRemoved(final Set<Capability> addedCaps) {
+        for (final Capability addedCap : addedCaps) {
+            capabilities.remove(addedCap.getCapabilityUri());
+        }
+        notifyListeners();
+    }
+
+    @Override
+    public synchronized void close() throws Exception {
+        listeners.clear();
+        sessions.clear();
+        capabilities.clear();
     }
 }