X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=opendaylight%2Fmd-sal%2Fsal-netconf-connector%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fcontroller%2Fsal%2Fconnect%2Fnetconf%2FNetconfDevice.java;h=a7ae9cb177a10ede9eebf2dab37812109996d00a;hb=e433e0aa67cc6d144cd3d8d6117de864eb7ebf97;hp=9a5b239024c5bb0cbca3798de58ccc102a994224;hpb=08351c185b20967cf3de414b16e97670149f5d51;p=controller.git diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDevice.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDevice.java index 9a5b239024..a7ae9cb177 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDevice.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDevice.java @@ -7,6 +7,7 @@ */ package org.opendaylight.controller.sal.connect.netconf; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Preconditions; @@ -20,12 +21,15 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import java.util.Collection; -import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; +import org.opendaylight.controller.md.sal.dom.api.DOMNotification; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcException; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcService; import org.opendaylight.controller.netconf.api.NetconfMessage; import org.opendaylight.controller.sal.connect.api.MessageTransformer; import org.opendaylight.controller.sal.connect.api.RemoteDevice; @@ -36,13 +40,14 @@ import org.opendaylight.controller.sal.connect.netconf.listener.NetconfDeviceCom import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionPreferences; import org.opendaylight.controller.sal.connect.netconf.sal.NetconfDeviceRpc; import org.opendaylight.controller.sal.connect.netconf.schema.NetconfRemoteSchemaYangSourceProvider; +import org.opendaylight.controller.sal.connect.netconf.schema.mapping.NetconfMessageTransformer; import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil; import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.extension.rev131210.$YangModuleInfoImpl; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfCapabilityChange; import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.fields.unavailable.capabilities.UnavailableCapability; +import org.opendaylight.yangtools.sal.binding.generator.impl.ModuleInfoBackedContext; import org.opendaylight.yangtools.yang.common.QName; -import org.opendaylight.yangtools.yang.common.RpcResult; -import org.opendaylight.yangtools.yang.data.api.CompositeNode; 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; @@ -63,6 +68,26 @@ public final class NetconfDevice implements RemoteDevice QNAME_TO_SOURCE_ID_FUNCTION = new Function() { @Override public SourceIdentifier apply(final QName input) { @@ -77,28 +102,37 @@ public final class NetconfDevice implements RemoteDevice salFacade; private final ListeningExecutorService processingExecutor; private final SchemaSourceRegistry schemaRegistry; - private final MessageTransformer messageTransformer; private final NetconfStateSchemas.NetconfStateSchemasResolver stateSchemasResolver; private final NotificationHandler notificationHandler; private final List> sourceRegistrations = Lists.newArrayList(); + // Message transformer is constructed once the schemas are available + private MessageTransformer messageTransformer; + public NetconfDevice(final SchemaResourcesDTO schemaResourcesDTO, final RemoteDeviceId id, final RemoteDeviceHandler salFacade, - final ExecutorService globalProcessingExecutor, final MessageTransformer messageTransformer) { - this(schemaResourcesDTO, id, salFacade, globalProcessingExecutor, messageTransformer, false); + final ExecutorService globalProcessingExecutor) { + this(schemaResourcesDTO, id, salFacade, globalProcessingExecutor, false); } + /** + * Create rpc implementation capable of handling RPC for monitoring and notifications even before the schemas of remote device are downloaded + */ + static NetconfDeviceRpc getRpcForInitialization(final NetconfDeviceCommunicator listener) { + return new NetconfDeviceRpc(INIT_SCHEMA_CTX, listener, new NetconfMessageTransformer(INIT_SCHEMA_CTX, false)); + } + + // FIXME reduce parameters public NetconfDevice(final SchemaResourcesDTO schemaResourcesDTO, final RemoteDeviceId id, final RemoteDeviceHandler salFacade, - final ExecutorService globalProcessingExecutor, final MessageTransformer messageTransformer, final boolean reconnectOnSchemasChange) { + final ExecutorService globalProcessingExecutor, final boolean reconnectOnSchemasChange) { this.id = id; this.reconnectOnSchemasChange = reconnectOnSchemasChange; this.schemaRegistry = schemaResourcesDTO.getSchemaRegistry(); - this.messageTransformer = messageTransformer; this.schemaContextFactory = schemaResourcesDTO.getSchemaContextFactory(); this.salFacade = salFacade; this.stateSchemasResolver = schemaResourcesDTO.getStateSchemasResolver(); this.processingExecutor = MoreExecutors.listeningDecorator(globalProcessingExecutor); - this.notificationHandler = new NotificationHandler(salFacade, messageTransformer, id); + this.notificationHandler = new NotificationHandler(salFacade, id); } @Override @@ -111,24 +145,23 @@ public final class NetconfDevice implements RemoteDevice sourceResolverFuture = processingExecutor.submit(task); if(shouldListenOnSchemaChange(remoteSessionCapabilities)) { - registerToBaseNetconfStream(deviceRpc, listener); + registerToBaseNetconfStream(initRpc, listener); } final FutureCallback resolvedSourceCallback = new FutureCallback() { @Override public void onSuccess(final DeviceSources result) { - addProvidedSourcesToSchemaRegistry(deviceRpc, result); + addProvidedSourcesToSchemaRegistry(initRpc, result); setUpSchema(result); } private void setUpSchema(final DeviceSources result) { - processingExecutor.submit(new RecursiveSchemaSetup(result, remoteSessionCapabilities, deviceRpc, listener)); + processingExecutor.submit(new RecursiveSchemaSetup(result, remoteSessionCapabilities, listener)); } @Override @@ -139,16 +172,17 @@ public final class NetconfDevice implements RemoteDevice> rpcResultListenableFuture = - deviceRpc.invokeRpc(NetconfMessageTransformUtil.CREATE_SUBSCRIPTION_RPC_QNAME, NetconfMessageTransformUtil.CREATE_SUBSCRIPTION_RPC_CONTENT); + // TODO check whether the model describing create subscription is present in schema + // Perhaps add a default schema context to support create-subscription if the model was not provided (same as what we do for base netconf operations in transformer) + final CheckedFuture rpcResultListenableFuture = + deviceRpc.invokeRpc(NetconfMessageTransformUtil.toPath(NetconfMessageTransformUtil.CREATE_SUBSCRIPTION_RPC_QNAME), NetconfMessageTransformUtil.CREATE_SUBSCRIPTION_RPC_CONTENT); final NotificationHandler.NotificationFilter filter = new NotificationHandler.NotificationFilter() { @Override - public Optional filterNotification(final CompositeNode notification) { + public Optional filterNotification(final DOMNotification notification) { if (isCapabilityChanged(notification)) { logger.info("{}: Schemas change detected, reconnecting", id); // Only disconnect is enough, the reconnecting nature of the connector will take care of reconnecting @@ -158,14 +192,14 @@ public final class NetconfDevice implements RemoteDevice>() { + Futures.addCallback(rpcResultListenableFuture, new FutureCallback() { @Override - public void onSuccess(final RpcResult result) { + public void onSuccess(final DOMRpcResult domRpcResult) { notificationHandler.addNotificationFilter(filter); } @@ -180,10 +214,14 @@ public final class NetconfDevice implements RemoteDevice transformer) { + messageTransformer = transformer; } private void addProvidedSourcesToSchemaRegistry(final NetconfDeviceRpc deviceRpc, final DeviceSources deviceSources) { @@ -217,12 +252,10 @@ public final class NetconfDevice implements RemoteDevice listener) { - return new NetconfDeviceRpc(listener, messageTransformer); - } - @Override public void onRemoteSessionDown() { + notificationHandler.onRemoteSchemaDown(); + salFacade.onDeviceDisconnected(); for (final SchemaSourceRegistration sourceRegistration : sourceRegistrations) { sourceRegistration.close(); @@ -231,7 +264,7 @@ public final class NetconfDevice implements RemoteDevice { + private final NetconfDeviceRpc deviceRpc; private final NetconfSessionPreferences remoteSessionCapabilities; private final RemoteDeviceId id; private final NetconfStateSchemas.NetconfStateSchemasResolver stateSchemasResolver; - public DeviceSourcesResolver(final NetconfDeviceRpc deviceRpc, final NetconfSessionPreferences remoteSessionCapabilities, final RemoteDeviceId id, final NetconfStateSchemas.NetconfStateSchemasResolver stateSchemasResolver) { + DeviceSourcesResolver(final NetconfDeviceRpc deviceRpc, final NetconfSessionPreferences remoteSessionCapabilities, + final RemoteDeviceId id, final NetconfStateSchemas.NetconfStateSchemasResolver stateSchemasResolver) { this.deviceRpc = deviceRpc; this.remoteSessionCapabilities = remoteSessionCapabilities; this.id = id; this.stateSchemasResolver = stateSchemasResolver; } + public DeviceSourcesResolver(final NetconfSessionPreferences remoteSessionCapabilities, final RemoteDeviceId id, final NetconfStateSchemas.NetconfStateSchemasResolver stateSchemasResolver, final NetconfDeviceRpc rpcForMonitoring) { + this(rpcForMonitoring, remoteSessionCapabilities, id, stateSchemasResolver); + } + @Override public DeviceSources call() throws Exception { - - final Set requiredSources = Sets.newHashSet(Collections2.transform( - remoteSessionCapabilities.getModuleBasedCaps(), QNAME_TO_SOURCE_ID_FUNCTION)); - - // If monitoring is not supported, we will still attempt to create schema, sources might be already provided final NetconfStateSchemas availableSchemas = stateSchemasResolver.resolve(deviceRpc, remoteSessionCapabilities, id); logger.debug("{}: Schemas exposed by ietf-netconf-monitoring: {}", id, availableSchemas.getAvailableYangSchemasQNames()); - final Set providedSources = Sets.newHashSet(Collections2.transform( - availableSchemas.getAvailableYangSchemasQNames(), QNAME_TO_SOURCE_ID_FUNCTION)); - - final Set requiredSourcesNotProvided = Sets.difference(requiredSources, providedSources); + final Set requiredSources = Sets.newHashSet(remoteSessionCapabilities.getModuleBasedCaps()); + final Set providedSources = availableSchemas.getAvailableYangSchemasQNames(); + final Set requiredSourcesNotProvided = Sets.difference(requiredSources, providedSources); if (!requiredSourcesNotProvided.isEmpty()) { logger.warn("{}: Netconf device does not provide all yang models reported in hello message capabilities, required but not provided: {}", id, requiredSourcesNotProvided); logger.warn("{}: Attempting to build schema context from required sources", id); } - - // TODO should we perform this ? We have a mechanism to fix initialization of devices not reporting or required modules in hello - // That is overriding capabilities in configuration using attribute yang-module-capabilities - // This is more user friendly even though it clashes with attribute yang-module-capabilities - // Some devices do not report all required models in hello message, but provide them - final Set providedSourcesNotRequired = Sets.difference(providedSources, requiredSources); + // Here all the sources reported in netconf monitoring are merged with those reported in hello. + // It is necessary to perform this since submodules are not mentioned in hello but still required. + // This clashes with the option of a user to specify supported yang models manually in configuration for netconf-connector + // and as a result one is not able to fully override yang models of a device. It is only possible to add additional models. + final Set providedSourcesNotRequired = Sets.difference(providedSources, requiredSources); if (!providedSourcesNotRequired.isEmpty()) { logger.warn("{}: Netconf device provides additional yang models not reported in hello message capabilities: {}", id, providedSourcesNotRequired); logger.warn("{}: Adding provided but not required sources as required to prevent failures", id); + logger.debug("{}: Netconf device reported in hello: {}", id, requiredSources); requiredSources.addAll(providedSourcesNotRequired); } @@ -323,25 +356,32 @@ public final class NetconfDevice implements RemoteDevice requiredSources; - private final Collection providedSources; + private final Set requiredSources; + private final Set providedSources; - public DeviceSources(final Collection requiredSources, final Collection providedSources) { + public DeviceSources(final Set requiredSources, final Set providedSources) { this.requiredSources = requiredSources; this.providedSources = providedSources; } - public Collection getRequiredSources() { + public Set getRequiredSourcesQName() { return requiredSources; } - public Collection getProvidedSources() { + public Set getProvidedSourcesQName() { return providedSources; } + public Collection getRequiredSources() { + return Collections2.transform(requiredSources, QNAME_TO_SOURCE_ID_FUNCTION); + } + + public Collection getProvidedSources() { + return Collections2.transform(providedSources, QNAME_TO_SOURCE_ID_FUNCTION); + } + } /** @@ -350,14 +390,12 @@ public final class NetconfDevice implements RemoteDevice listener; - private NetconfDeviceCapabilities capabilities; + private final NetconfDeviceCapabilities capabilities; - public RecursiveSchemaSetup(final DeviceSources deviceSources, final NetconfSessionPreferences remoteSessionCapabilities, final NetconfDeviceRpc deviceRpc, final RemoteDeviceCommunicator listener) { + public RecursiveSchemaSetup(final DeviceSources deviceSources, final NetconfSessionPreferences remoteSessionCapabilities, final RemoteDeviceCommunicator listener) { this.deviceSources = deviceSources; this.remoteSessionCapabilities = remoteSessionCapabilities; - this.deviceRpc = deviceRpc; this.listener = listener; this.capabilities = remoteSessionCapabilities.getNetconfDeviceCapabilities(); } @@ -376,7 +414,9 @@ public final class NetconfDevice implements RemoteDevice filteredQNames = Sets.difference(remoteSessionCapabilities.getModuleBasedCaps(), capabilities.getUnresolvedCapabilites().keySet()); + final Collection filteredQNames = Sets.difference(deviceSources.getProvidedSourcesQName(), capabilities.getUnresolvedCapabilites().keySet()); capabilities.addCapabilities(filteredQNames); capabilities.addNonModuleBasedCapabilities(remoteSessionCapabilities.getNonModuleCaps()); - handleSalInitializationSuccess(result, remoteSessionCapabilities, deviceRpc); + handleSalInitializationSuccess(result, remoteSessionCapabilities, getDeviceSpecificRpc(result)); } @Override @@ -420,6 +460,10 @@ public final class NetconfDevice implements RemoteDevice stripMissingSource(final Collection requiredSources, final SourceIdentifier sIdToRemove) { final LinkedList sourceIdentifiers = Lists.newLinkedList(requiredSources); final boolean removed = sourceIdentifiers.remove(sIdToRemove); @@ -427,28 +471,37 @@ public final class NetconfDevice implements RemoteDevice getQNameFromSourceIdentifiers(Collection identifiers) { - Collection qNames = new HashSet<>(); - for (SourceIdentifier source : identifiers) { - Optional qname = getQNameFromSourceIdentifier(source); - if (qname.isPresent()) { - qNames.add(qname.get()); + private Collection getQNameFromSourceIdentifiers(final Collection identifiers) { + final Collection qNames = Collections2.transform(identifiers, new Function() { + @Override + public QName apply(final SourceIdentifier sourceIdentifier) { + return getQNameFromSourceIdentifier(sourceIdentifier); } - } + }); + if (qNames.isEmpty()) { logger.debug("Unable to map any source identfiers to a capability reported by device : " + identifiers); } return qNames; } - private Optional getQNameFromSourceIdentifier(SourceIdentifier identifier) { - for (QName qname : remoteSessionCapabilities.getModuleBasedCaps()) { - if (qname.getLocalName().equals(identifier.getName()) - && qname.getFormattedRevision().equals(identifier.getRevision())) { - return Optional.of(qname); + private QName getQNameFromSourceIdentifier(final SourceIdentifier identifier) { + // Required sources are all required and provided merged in DeviceSourcesResolver + for (final QName qname : deviceSources.getRequiredSourcesQName()) { + if(qname.getLocalName().equals(identifier.getName()) == false) { + continue; + } + + if(identifier.getRevision().equals(SourceIdentifier.NOT_PRESENT_FORMATTED_REVISION) && + qname.getRevision() == null) { + return qname; + } + + if (qname.getFormattedRevision().equals(identifier.getRevision())) { + return qname; } } - throw new IllegalArgumentException("Unable to map identifier to a devices reported capability: " + identifier); + throw new IllegalArgumentException("Unable to map identifier to a devices reported capability: " + identifier + " Available: " + deviceSources.getRequiredSourcesQName()); } } }