+ notificationHandler.handleNotification(notification);
+ }
+
+ /**
+ * Just a transfer object containing schema related dependencies. Injected in constructor.
+ */
+ public static class SchemaResourcesDTO {
+ private final SchemaSourceRegistry schemaRegistry;
+ private final SchemaContextFactory schemaContextFactory;
+ private final NetconfStateSchemas.NetconfStateSchemasResolver stateSchemasResolver;
+
+ public SchemaResourcesDTO(final SchemaSourceRegistry schemaRegistry, final SchemaContextFactory schemaContextFactory, final NetconfStateSchemas.NetconfStateSchemasResolver stateSchemasResolver) {
+ this.schemaRegistry = Preconditions.checkNotNull(schemaRegistry);
+ this.schemaContextFactory = Preconditions.checkNotNull(schemaContextFactory);
+ this.stateSchemasResolver = Preconditions.checkNotNull(stateSchemasResolver);
+ }
+
+ public SchemaSourceRegistry getSchemaRegistry() {
+ return schemaRegistry;
+ }
+
+ public SchemaContextFactory getSchemaContextFactory() {
+ return schemaContextFactory;
+ }
+
+ public NetconfStateSchemas.NetconfStateSchemasResolver getStateSchemasResolver() {
+ return stateSchemasResolver;
+ }
+ }
+
+ /**
+ * Schema building callable.
+ */
+ private static class DeviceSourcesResolver implements Callable<DeviceSources> {
+ private final NetconfDeviceRpc deviceRpc;
+ private final NetconfSessionCapabilities remoteSessionCapabilities;
+ private final RemoteDeviceId id;
+ private final NetconfStateSchemas.NetconfStateSchemasResolver stateSchemasResolver;
+
+ public DeviceSourcesResolver(final NetconfDeviceRpc deviceRpc, final NetconfSessionCapabilities remoteSessionCapabilities, final RemoteDeviceId id, final NetconfStateSchemas.NetconfStateSchemasResolver stateSchemasResolver) {
+ this.deviceRpc = deviceRpc;
+ this.remoteSessionCapabilities = remoteSessionCapabilities;
+ this.id = id;
+ this.stateSchemasResolver = stateSchemasResolver;
+ }
+
+ @Override
+ public DeviceSources call() throws Exception {
+
+ final Set<SourceIdentifier> 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<SourceIdentifier> providedSources = Sets.newHashSet(Collections2.transform(
+ availableSchemas.getAvailableYangSchemasQNames(), QNAME_TO_SOURCE_ID_FUNCTION));
+
+ final Set<SourceIdentifier> 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<SourceIdentifier> 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);
+ requiredSources.addAll(providedSourcesNotRequired);
+ }
+
+ return new DeviceSources(requiredSources, providedSources);
+ }
+ }
+
+ /**
+ * Contains RequiredSources - sources from capabilities.
+ *
+ */
+ private static final class DeviceSources {
+ private final Collection<SourceIdentifier> requiredSources;
+ private final Collection<SourceIdentifier> providedSources;
+
+ public DeviceSources(final Collection<SourceIdentifier> requiredSources, final Collection<SourceIdentifier> providedSources) {
+ this.requiredSources = requiredSources;
+ this.providedSources = providedSources;
+ }
+
+ public Collection<SourceIdentifier> getRequiredSources() {
+ return requiredSources;
+ }
+
+ public Collection<SourceIdentifier> getProvidedSources() {
+ return providedSources;
+ }
+
+ }
+
+ /**
+ * Schema builder that tries to build schema context from provided sources or biggest subset of it.
+ */
+ private final class RecursiveSchemaSetup implements Runnable {
+ private final DeviceSources deviceSources;
+ private final NetconfSessionCapabilities remoteSessionCapabilities;
+ private final NetconfDeviceRpc deviceRpc;
+ private final RemoteDeviceCommunicator<NetconfMessage> listener;
+
+ public RecursiveSchemaSetup(final DeviceSources deviceSources, final NetconfSessionCapabilities remoteSessionCapabilities, final NetconfDeviceRpc deviceRpc, final RemoteDeviceCommunicator<NetconfMessage> listener) {
+ this.deviceSources = deviceSources;
+ this.remoteSessionCapabilities = remoteSessionCapabilities;
+ this.deviceRpc = deviceRpc;
+ this.listener = listener;
+ }
+
+ @Override
+ public void run() {
+ setUpSchema(deviceSources.getRequiredSources());
+ }
+
+ /**
+ * Recursively build schema context, in case of success or final failure notify device
+ */
+ private void setUpSchema(final Collection<SourceIdentifier> requiredSources) {
+ logger.trace("{}: Trying to build schema context from {}", id, requiredSources);
+
+ // If no more sources, fail
+ if(requiredSources.isEmpty()) {
+ handleSalInitializationFailure(new IllegalStateException(id + ": No more sources for schema context"), listener);
+ return;
+ }
+
+ final CheckedFuture<SchemaContext, SchemaResolutionException> schemaBuilderFuture = schemaContextFactory.createSchemaContext(requiredSources);
+
+ final FutureCallback<SchemaContext> RecursiveSchemaBuilderCallback = new FutureCallback<SchemaContext>() {
+
+ @Override
+ public void onSuccess(final SchemaContext result) {
+ logger.debug("{}: Schema context built successfully from {}", id, requiredSources);
+ handleSalInitializationSuccess(result, remoteSessionCapabilities, deviceRpc);
+ }
+
+ @Override
+ public void onFailure(final Throwable t) {
+ // In case source missing, try without it
+ if (t instanceof MissingSchemaSourceException) {
+ final SourceIdentifier missingSource = ((MissingSchemaSourceException) t).getSourceId();
+ logger.warn("{}: Unable to build schema context, missing source {}, will reattempt without it", id, missingSource);
+ setUpSchema(stripMissingSource(requiredSources, missingSource));
+
+ // In case resolution error, try only with resolved sources
+ } else if (t instanceof SchemaResolutionException) {
+ // TODO check for infinite loop
+ final SchemaResolutionException resolutionException = (SchemaResolutionException) t;
+ logger.warn("{}: Unable to build schema context, unsatisfied imports {}, will reattempt with resolved only", id, resolutionException.getUnsatisfiedImports());
+ setUpSchema(resolutionException.getResolvedSources());
+ // unknown error, fail
+ } else {
+ handleSalInitializationFailure(t, listener);
+ }
+ }
+ };
+
+ Futures.addCallback(schemaBuilderFuture, RecursiveSchemaBuilderCallback);
+ }
+
+ private Collection<SourceIdentifier> stripMissingSource(final Collection<SourceIdentifier> requiredSources, final SourceIdentifier sIdToRemove) {
+ final LinkedList<SourceIdentifier> sourceIdentifiers = Lists.newLinkedList(requiredSources);
+ final boolean removed = sourceIdentifiers.remove(sIdToRemove);
+ Preconditions.checkState(removed, "{}: Trying to remove {} from {} failed", id, sIdToRemove, requiredSources);
+ return sourceIdentifiers;
+ }