.setSchemaAssembler(schemaAssembler)
.setTopologyId(topologyId)
.setNetconfClientFactory(clientFactory)
- .setSchemaResourceDTO(resourceManager.getSchemaResources(netconfNode.getSchemaCacheDirectory(), deviceId))
+ .setDeviceSchemaProvider(resourceManager.getSchemaResources(netconfNode.getSchemaCacheDirectory(),
+ deviceId))
.setIdleTimeout(writeTxIdleTimeout)
.build();
}
import org.opendaylight.mdsal.dom.api.DOMMountPointService;
import org.opendaylight.mdsal.dom.api.DOMRpcResult;
import org.opendaylight.mdsal.dom.api.DOMRpcService;
-import org.opendaylight.netconf.client.mdsal.NetconfDevice.SchemaResourcesDTO;
+import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchemaProvider;
import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceServices;
import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceServices.Actions;
private final Duration writeTxIdleTimeout;
private final DOMMountPointService mountPointService;
- private SchemaResourcesDTO schemaResources;
+ private DeviceNetconfSchemaProvider schemaProvider;
private Timeout actorResponseWaitTime;
private RemoteDeviceId id;
private List<SourceIdentifier> sourceIdentifiers = null;
protected NetconfNodeActor(final NetconfTopologySetup setup, final RemoteDeviceId id,
final Timeout actorResponseWaitTime, final DOMMountPointService mountPointService) {
this.id = id;
- schemaResources = setup.getSchemaResourcesDTO();
+ schemaProvider = setup.getDeviceSchemaProvider();
this.actorResponseWaitTime = actorResponseWaitTime;
writeTxIdleTimeout = setup.getIdleTimeout();
this.mountPointService = mountPointService;
LOG.debug("{}: Master is ready.", id);
} else if (message instanceof RefreshSetupMasterActorData masterActorData) {
id = masterActorData.getRemoteDeviceId();
- schemaResources = masterActorData.getNetconfTopologyDeviceSetup().getSchemaResourcesDTO();
+ schemaProvider = masterActorData.getNetconfTopologyDeviceSetup().getDeviceSchemaProvider();
sender().tell(new MasterActorDataInitialized(), self());
} else if (message instanceof AskForMasterMountPoint askForMasterMountPoint) { // master
// only master contains reference to deviceDataBroker
} else if (message instanceof RefreshSlaveActor refreshSlave) { //slave
actorResponseWaitTime = refreshSlave.getActorResponseWaitTime();
id = refreshSlave.getId();
- schemaResources = refreshSlave.getSetup().getSchemaResourcesDTO();
+ schemaProvider = refreshSlave.getSetup().getDeviceSchemaProvider();
} else if (message instanceof NetconfDataTreeServiceRequest) {
final var netconfActor = context().actorOf(NetconfDataTreeServiceActor.props(netconfService,
writeTxIdleTimeout));
}
private void sendYangTextSchemaSourceProxy(final SourceIdentifier sourceIdentifier, final ActorRef sender) {
- Futures.addCallback(
- schemaResources.getSchemaRepository().getSchemaSource(sourceIdentifier, YangTextSource.class),
+ Futures.addCallback(schemaProvider.repository().getSchemaSource(sourceIdentifier, YangTextSource.class),
new FutureCallback<>() {
@Override
public void onSuccess(final YangTextSource yangTextSchemaSource) {
final var remoteYangTextSourceProvider = new ProxyYangTextSourceProvider(masterReference, dispatcher,
actorResponseWaitTime);
final var remoteProvider = new RemoteSchemaProvider(remoteYangTextSourceProvider, dispatcher);
- final var schemaRegistry = schemaResources.getSchemaRegistry();
+ final var schemaRegistry = schemaProvider.registry();
registeredSchemas = sourceIdentifiers.stream()
.map(sourceId -> schemaRegistry.registerSchemaSource(remoteProvider, PotentialSchemaSource.create(sourceId,
YangTextSource.class, PotentialSchemaSource.Costs.REMOTE_IO.getValue())))
.collect(Collectors.toList());
- return schemaResources.getSchemaRepository().createEffectiveModelContextFactory();
+ return schemaProvider.repository().createEffectiveModelContextFactory();
}
private void resolveSchemaContext(final EffectiveModelContextFactory schemaContextFactory,
import org.opendaylight.mdsal.binding.api.DataBroker;
import org.opendaylight.mdsal.singleton.api.ClusterSingletonServiceProvider;
import org.opendaylight.netconf.client.NetconfClientFactory;
-import org.opendaylight.netconf.client.mdsal.NetconfDevice;
import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchemaProvider;
+import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchemaProvider;
import org.opendaylight.netconf.common.NetconfTimer;
import org.opendaylight.netconf.topology.spi.NetconfTopologySchemaAssembler;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
private final ActorSystem actorSystem;
private final NetconfClientFactory netconfClientFactory;
private final String topologyId;
- private final NetconfDevice.SchemaResourcesDTO schemaResourceDTO;
+ private final DeviceNetconfSchemaProvider deviceSchemaProvider;
private final Duration idleTimeout;
private final BaseNetconfSchemaProvider baseSchemaProvider;
actorSystem = builder.getActorSystem();
netconfClientFactory = builder.getNetconfClientFactory();
topologyId = builder.getTopologyId();
- schemaResourceDTO = builder.getSchemaResourceDTO();
+ deviceSchemaProvider = builder.getDeviceSchemaProvider();
idleTimeout = builder.getIdleTimeout();
baseSchemaProvider = builder.getBaseSchemaProvider();
}
return netconfClientFactory;
}
- public NetconfDevice.SchemaResourcesDTO getSchemaResourcesDTO() {
- return schemaResourceDTO;
+ public DeviceNetconfSchemaProvider getDeviceSchemaProvider() {
+ return deviceSchemaProvider;
}
public Duration getIdleTimeout() {
private ActorSystem actorSystem;
private String topologyId;
private NetconfClientFactory netconfClientFactory;
- private NetconfDevice.SchemaResourcesDTO schemaResourceDTO;
+ private DeviceNetconfSchemaProvider deviceSchemaProvider;
private Duration idleTimeout;
private BaseNetconfSchemaProvider baseSchemaProvider;
return this;
}
- public Builder setSchemaResourceDTO(
- final NetconfDevice.SchemaResourcesDTO schemaResourceDTO) {
- this.schemaResourceDTO = schemaResourceDTO;
+ public Builder setDeviceSchemaProvider(final DeviceNetconfSchemaProvider deviceSchemaProvider) {
+ this.deviceSchemaProvider = deviceSchemaProvider;
return this;
}
- NetconfDevice.SchemaResourcesDTO getSchemaResourceDTO() {
- return schemaResourceDTO;
+ DeviceNetconfSchemaProvider getDeviceSchemaProvider() {
+ return deviceSchemaProvider;
}
public Builder setIdleTimeout(final Duration idleTimeout) {
new SimpleDOMEntityOwnershipService());
final var resources = resourceManager.getSchemaResources(TEST_DEFAULT_SUBDIR, "test");
- resources.getSchemaRegistry().registerSchemaSource(
+ resources.registry().registerSchemaSource(
id -> Futures.immediateFuture(new DelegatedYangTextSource(id, topModuleInfo.getYangTextCharSource())),
PotentialSchemaSource.create(new SourceIdentifier(TOP_MODULE_NAME,
topModuleInfo.getName().getRevision().map(Revision::toString).orElse(null)),
import org.opendaylight.mdsal.dom.api.DOMSchemaService;
import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult;
import org.opendaylight.mdsal.dom.spi.SimpleDOMActionResult;
-import org.opendaylight.netconf.client.mdsal.NetconfDevice.SchemaResourcesDTO;
+import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchemaProvider;
import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceServices;
import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceServices.Actions;
@Mock
private EffectiveModelContext mockSchemaContext;
@Mock
- private SchemaResourcesDTO schemaResourceDTO;
+ private DeviceNetconfSchemaProvider deviceSchemaProvider;
@Before
public void setup() {
final NetconfTopologySetup setup = NetconfTopologySetup.builder()
.setActorSystem(system)
.setIdleTimeout(Duration.ofSeconds(1))
- .setSchemaResourceDTO(schemaResourceDTO)
+ .setDeviceSchemaProvider(deviceSchemaProvider)
.setBaseSchemaProvider(BASE_SCHEMAS)
.build();
final NetconfTopologySetup newSetup = NetconfTopologySetup.builder()
.setBaseSchemaProvider(BASE_SCHEMAS)
- .setSchemaResourceDTO(schemaResourceDTO)
+ .setDeviceSchemaProvider(deviceSchemaProvider)
.setActorSystem(system)
.build();
verify(newMockSchemaSourceReg).close();
}
- @SuppressWarnings("unchecked")
+// @SuppressWarnings("unchecked")
@Test
public void testRegisterMountPointWithSchemaFailures() throws Exception {
- SchemaResourcesDTO schemaResourceDTO2 = mock(SchemaResourcesDTO.class);
- doReturn(mockRegistry).when(schemaResourceDTO2).getSchemaRegistry();
- doReturn(mockSchemaRepository).when(schemaResourceDTO2).getSchemaRepository();
+ var deviceSchemaProvider2 = mock(DeviceNetconfSchemaProvider.class);
+ doReturn(mockRegistry).when(deviceSchemaProvider2).registry();
+ doReturn(mockSchemaRepository).when(deviceSchemaProvider2).repository();
final NetconfTopologySetup setup = NetconfTopologySetup.builder()
- .setSchemaResourceDTO(schemaResourceDTO2)
+ .setDeviceSchemaProvider(deviceSchemaProvider2)
.setBaseSchemaProvider(BASE_SCHEMAS)
.setActorSystem(system)
.build();
public void testMissingSchemaSourceOnMissingProvider() throws Exception {
final var repository = new SharedSchemaRepository("test");
- final var schemaResourceDTO2 = mock(SchemaResourcesDTO.class);
- doReturn(repository).when(schemaResourceDTO2).getSchemaRepository();
+ final var deviceSchemaProvider2 = mock(DeviceNetconfSchemaProvider.class);
+ doReturn(repository).when(deviceSchemaProvider2).repository();
final var setup = NetconfTopologySetup.builder()
.setActorSystem(system)
- .setSchemaResourceDTO(schemaResourceDTO2)
+ .setDeviceSchemaProvider(deviceSchemaProvider2)
.setIdleTimeout(Duration.ofSeconds(1))
.setBaseSchemaProvider(BASE_SCHEMAS)
.build();
@Test
public void testYangTextSchemaSourceRequest() throws Exception {
- doReturn(masterSchemaRepository).when(schemaResourceDTO).getSchemaRepository();
+ doReturn(masterSchemaRepository).when(deviceSchemaProvider).repository();
final var sourceIdentifier = new SourceIdentifier("testID");
}
private ActorRef registerSlaveMountPoint() {
- SchemaResourcesDTO schemaResourceDTO2 = mock(SchemaResourcesDTO.class);
- doReturn(mockRegistry).when(schemaResourceDTO2).getSchemaRegistry();
- doReturn(mockSchemaRepository).when(schemaResourceDTO2).getSchemaRepository();
+ var deviceSchemaProvider2 = mock(DeviceNetconfSchemaProvider.class);
+ doReturn(mockRegistry).when(deviceSchemaProvider2).registry();
+ doReturn(mockSchemaRepository).when(deviceSchemaProvider2).repository();
final ActorRef slaveRef = system.actorOf(NetconfNodeActor.props(NetconfTopologySetup.builder()
- .setSchemaResourceDTO(schemaResourceDTO2)
+ .setDeviceSchemaProvider(deviceSchemaProvider2)
.setActorSystem(system)
.setBaseSchemaProvider(BASE_SCHEMAS)
.build(), remoteDeviceId, TIMEOUT, mockMountPointService));
import org.opendaylight.mdsal.dom.api.DOMMountPointService;
import org.opendaylight.mdsal.dom.api.DOMNotificationService;
import org.opendaylight.mdsal.dom.api.DOMRpcService;
-import org.opendaylight.netconf.client.mdsal.NetconfDevice;
+import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchemaProvider;
import org.opendaylight.netconf.client.mdsal.api.NetconfDeviceSchemasResolver;
import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceServices;
NetconfTopologySetup masterSetup = NetconfTopologySetup.builder()
.setActorSystem(masterSystem)
.setDataBroker(mockDataBroker)
- .setSchemaResourceDTO(new NetconfDevice.SchemaResourcesDTO(
- masterSchemaRepository, masterSchemaRepository, mockSchemaContextFactory, mockSchemasResolver))
.setBaseSchemaProvider(BASE_SCHEMAS)
+ .setDeviceSchemaProvider(createDeviceSchemaProvider(masterSchemaRepository))
.build();
testMasterActorRef = TestActorRef.create(masterSystem, Props.create(TestMasterActor.class, masterSetup,
NetconfTopologySetup slaveSetup = NetconfTopologySetup.builder()
.setActorSystem(slaveSystem)
.setDataBroker(mockDataBroker)
- .setSchemaResourceDTO(new NetconfDevice.SchemaResourcesDTO(
- slaveSchemaRepository, slaveSchemaRepository, mockSchemaContextFactory, mockSchemasResolver))
.setBaseSchemaProvider(BASE_SCHEMAS)
+ .setDeviceSchemaProvider(createDeviceSchemaProvider(slaveSchemaRepository))
.build();
netconfNodeManager = new NetconfNodeManager(slaveSetup, DEVICE_ID, responseTimeout,
setupMountPointMocks();
}
+ private static DeviceNetconfSchemaProvider createDeviceSchemaProvider(final SharedSchemaRepository repository) {
+ final var provider = mock(DeviceNetconfSchemaProvider.class);
+ doReturn(repository).when(provider).registry();
+ doReturn(repository).when(provider).repository();
+ return provider;
+ }
+
@After
public void teardown() {
TestKit.shutdownActorSystem(slaveSystem, true);
import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
import org.opendaylight.netconf.client.mdsal.LibraryModulesSchemas;
import org.opendaylight.netconf.client.mdsal.LibrarySchemaSourceProvider;
-import org.opendaylight.netconf.client.mdsal.NetconfDevice.SchemaResourcesDTO;
import org.opendaylight.netconf.client.mdsal.NetconfDeviceBuilder;
import org.opendaylight.netconf.client.mdsal.NetconfDeviceCommunicator;
import org.opendaylight.netconf.client.mdsal.NetconfDeviceSchema;
import org.opendaylight.netconf.client.mdsal.SchemalessNetconfDevice;
import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchemaProvider;
import org.opendaylight.netconf.client.mdsal.api.DeviceActionFactory;
+import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchemaProvider;
import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
import org.opendaylight.netconf.client.mdsal.api.RemoteDevice;
import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceHandler;
final var resources = schemaManager.getSchemaResources(node.getSchemaCacheDirectory(), nodeId.getValue());
device = new NetconfDeviceBuilder()
.setReconnectOnSchemasChange(node.requireReconnectOnChangedSchema())
- .setSchemaResourcesDTO(resources)
- .setGlobalProcessingExecutor(schemaAssembler.executor())
+ .setBaseSchemaProvider(baseSchemaProvider)
+ .setDeviceSchemaProvider(resources)
+ .setProcessingExecutor(schemaAssembler.executor())
.setId(deviceId)
.setSalFacade(salFacade)
.setDeviceActionFactory(deviceActionFactory)
- .setBaseSchemas(baseSchemaProvider)
.build();
yanglibRegistrations = registerDeviceSchemaSources(deviceId, node, resources);
}
}
private static List<Registration> registerDeviceSchemaSources(final RemoteDeviceId remoteDeviceId,
- final NetconfNode node, final SchemaResourcesDTO resources) {
+ final NetconfNode node, final DeviceNetconfSchemaProvider resources) {
final var yangLibrary = node.getYangLibrary();
if (yangLibrary != null) {
final Uri uri = yangLibrary.getYangLibraryUrl();
if (uri != null) {
final var registrations = new ArrayList<Registration>();
final var yangLibURL = uri.getValue();
- final var schemaRegistry = resources.getSchemaRegistry();
// pre register yang library sources as fallback schemas to schema registry
final var yangLibUsername = yangLibrary.getUsername();
? LibraryModulesSchemas.create(yangLibURL, yangLibUsername, yangLigPassword)
: LibraryModulesSchemas.create(yangLibURL);
+ final var registry = resources.registry();
for (var entry : schemas.getAvailableModels().entrySet()) {
- registrations.add(schemaRegistry.registerSchemaSource(new LibrarySchemaSourceProvider(
+ registrations.add(registry.registerSchemaSource(new LibrarySchemaSourceProvider(
remoteDeviceId, schemas.getAvailableModels()),
PotentialSchemaSource.create(entry.getKey(), YangTextSource.class,
PotentialSchemaSource.Costs.REMOTE_IO.getValue())));
import org.opendaylight.mdsal.binding.runtime.spi.BindingRuntimeHelpers;
import org.opendaylight.mdsal.dom.api.DOMRpcResult;
import org.opendaylight.netconf.client.mdsal.api.NetconfDeviceSchemas;
+import org.opendaylight.netconf.client.mdsal.api.NetconfRpcService;
import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
import org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil;
-import org.opendaylight.netconf.client.mdsal.spi.NetconfDeviceRpc;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.Get;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.ModulesState;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.YangLibrary;
}
// FIXME: this should work on NetconfRpcService
- public static ListenableFuture<LibraryModulesSchemas> forDevice(final NetconfDeviceRpc deviceRpc,
+ public static ListenableFuture<LibraryModulesSchemas> forDevice(final NetconfRpcService deviceRpc,
final RemoteDeviceId deviceId) {
final var future = SettableFuture.<LibraryModulesSchemas>create();
Futures.addCallback(deviceRpc.invokeNetconf(Get.QNAME, GET_MODULES_STATE_MODULE_LIST_RPC),
import java.util.Optional;
import javax.xml.transform.dom.DOMSource;
import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.mdsal.dom.api.DOMRpcService;
+import org.opendaylight.netconf.client.mdsal.api.NetconfRpcService;
import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.GetSchema;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.Yang;
private static final LeafNode<?> FORMAT_LEAF = ImmutableNodes.leafNode(FORMAT_PATHARG, Yang.QNAME);
private static final NodeIdentifier NETCONF_DATA_PATHARG = NodeIdentifier.create(Data.QNAME);
- private final DOMRpcService rpc;
+ private final NetconfRpcService rpc;
private final RemoteDeviceId id;
- public MonitoringSchemaSourceProvider(final RemoteDeviceId id, final DOMRpcService rpc) {
+ public MonitoringSchemaSourceProvider(final RemoteDeviceId id, final NetconfRpcService rpc) {
this.id = requireNonNull(id);
this.rpc = requireNonNull(rpc);
}
@Override
public ListenableFuture<YangTextSource> getSource(final SourceIdentifier sourceIdentifier) {
final String moduleName = sourceIdentifier.name().getLocalName();
+ final Revision revision = sourceIdentifier.revision();
+ final ContainerNode getSchemaRequest = createGetSchemaRequest(moduleName,
+ revision == null ? Optional.empty() : Optional.of(revision.toString()));
- final Optional<String> revision = Optional.ofNullable(sourceIdentifier.revision()).map(Revision::toString);
- final ContainerNode getSchemaRequest = createGetSchemaRequest(moduleName, revision);
LOG.trace("{}: Loading YANG schema source for {}:{}", id, moduleName, revision);
- return Futures.transform(
- rpc.invokeRpc(GetSchema.QNAME, getSchemaRequest), result -> {
+ return Futures.transform(rpc.invokeNetconf(GetSchema.QNAME, getSchemaRequest),
+ result -> {
// Transform composite node to string schema representation and then to ASTSchemaSource.
if (result.errors().isEmpty()) {
final String schemaString = getSchemaFromRpc(id, result.value())
*/
package org.opendaylight.netconf.client.mdsal;
-import static com.google.common.base.Preconditions.checkState;
import static java.util.Objects.requireNonNull;
import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.NETCONF_GET_NODEID;
-import com.google.common.base.Predicates;
-import com.google.common.collect.Collections2;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
-import com.google.common.util.concurrent.SettableFuture;
-import java.io.Serial;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
import java.util.concurrent.CancellationException;
-import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
-import java.util.stream.Collectors;
import org.checkerframework.checker.lock.qual.GuardedBy;
import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.mdsal.dom.api.DOMRpcResult;
-import org.opendaylight.netconf.api.CapabilityURN;
import org.opendaylight.netconf.api.messages.NetconfMessage;
import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchema;
import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchemaProvider;
import org.opendaylight.netconf.client.mdsal.api.DeviceActionFactory;
-import org.opendaylight.netconf.client.mdsal.api.NetconfDeviceSchemasResolver;
+import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchemaProvider;
+import org.opendaylight.netconf.client.mdsal.api.NetconfRpcService;
import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
import org.opendaylight.netconf.client.mdsal.api.RemoteDevice;
import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceCommunicator;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.CreateSubscription;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.CreateSubscriptionInput;
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.device.rev240120.connection.oper.available.capabilities.AvailableCapability;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.available.capabilities.AvailableCapabilityBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.unavailable.capabilities.UnavailableCapability;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.unavailable.capabilities.UnavailableCapability.FailureReason;
-import org.opendaylight.yangtools.concepts.Registration;
import org.opendaylight.yangtools.rfc8528.model.api.SchemaMountConstants;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.MountPointContext;
import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
-import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier;
-import org.opendaylight.yangtools.yang.model.api.source.YangTextSource;
-import org.opendaylight.yangtools.yang.model.repo.api.EffectiveModelContextFactory;
-import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException;
-import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository;
-import org.opendaylight.yangtools.yang.model.repo.api.SchemaResolutionException;
-import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
*/
public class NetconfDevice implements RemoteDevice<NetconfDeviceCommunicator> {
private 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.of(
NodeIdentifier.create(RFC8528_SCHEMA_MOUNTS_QNAME));
protected final RemoteDeviceId id;
- protected final EffectiveModelContextFactory schemaContextFactory;
- protected final SchemaSourceRegistry schemaRegistry;
- protected final SchemaRepository schemaRepository;
-
- protected final List<Registration> sourceRegistrations = new ArrayList<>();
+ private final BaseNetconfSchemaProvider baseSchemaProvider;
+ private final DeviceNetconfSchemaProvider deviceSchemaProvider;
+ private final Executor processingExecutor;
private final RemoteDeviceHandler salFacade;
- private final Executor processingExecutor;
private final DeviceActionFactory deviceActionFactory;
- private final NetconfDeviceSchemasResolver stateSchemasResolver;
private final NotificationHandler notificationHandler;
private final boolean reconnectOnSchemasChange;
- private final BaseNetconfSchemaProvider baseSchemas;
@GuardedBy("this")
- private ListenableFuture<List<Object>> schemaFuturesList;
+ private ListenableFuture<?> schemaFuture;
@GuardedBy("this")
private boolean connected = false;
- public NetconfDevice(final SchemaResourcesDTO schemaResourcesDTO, final BaseNetconfSchemaProvider baseSchemas,
- final RemoteDeviceId id, final RemoteDeviceHandler salFacade, final Executor globalProcessingExecutor,
- final boolean reconnectOnSchemasChange) {
- this(schemaResourcesDTO, baseSchemas, id, salFacade, globalProcessingExecutor, reconnectOnSchemasChange, null);
+ public NetconfDevice(final RemoteDeviceId id,final BaseNetconfSchemaProvider baseSchemaProvider,
+ final DeviceNetconfSchemaProvider deviceSchemaProvider, final RemoteDeviceHandler salFacade,
+ final Executor globalProcessingExecutor, final boolean reconnectOnSchemasChange) {
+ this(id, baseSchemaProvider, deviceSchemaProvider, salFacade, globalProcessingExecutor,
+ reconnectOnSchemasChange, null);
}
- public NetconfDevice(final SchemaResourcesDTO schemaResourcesDTO, final BaseNetconfSchemaProvider baseSchemas,
- final RemoteDeviceId id, final RemoteDeviceHandler salFacade, final Executor globalProcessingExecutor,
- final boolean reconnectOnSchemasChange, final DeviceActionFactory deviceActionFactory) {
- this.baseSchemas = requireNonNull(baseSchemas);
- this.id = id;
+ public NetconfDevice(final RemoteDeviceId id, final BaseNetconfSchemaProvider baseSchemaProvider,
+ final DeviceNetconfSchemaProvider deviceSchemaProvider, final RemoteDeviceHandler salFacade,
+ final Executor globalProcessingExecutor, final boolean reconnectOnSchemasChange,
+ final DeviceActionFactory deviceActionFactory) {
+ this.id = requireNonNull(id);
+ this.baseSchemaProvider = requireNonNull(baseSchemaProvider);
+ this.deviceSchemaProvider = requireNonNull(deviceSchemaProvider);
this.reconnectOnSchemasChange = reconnectOnSchemasChange;
this.deviceActionFactory = deviceActionFactory;
- schemaRegistry = schemaResourcesDTO.getSchemaRegistry();
- schemaRepository = schemaResourcesDTO.getSchemaRepository();
- schemaContextFactory = schemaResourcesDTO.getSchemaContextFactory();
this.salFacade = salFacade;
- stateSchemasResolver = schemaResourcesDTO.getStateSchemasResolver();
processingExecutor = requireNonNull(globalProcessingExecutor);
notificationHandler = new NotificationHandler(salFacade, id);
}
@Override
public synchronized void onRemoteSessionUp(final NetconfSessionPreferences remoteSessionCapabilities,
final NetconfDeviceCommunicator listener) {
- // SchemaContext setup has to be performed in a dedicated thread since we are in a Netty thread in this method
+ // EffectiveModelContext setup has to be performed in a dedicated thread since we are in a Netty thread in this
+ // method.
// YANG models are being downloaded in this method and it would cause a deadlock if we used the netty thread
// https://netty.io/wiki/thread-model.html
- setConnected(true);
+ connected = true;
LOG.debug("{}: Session to remote device established with {}", id, remoteSessionCapabilities);
- final var baseSchema = baseSchemas.baseSchemaForCapabilities(remoteSessionCapabilities);
+ final var baseSchema = baseSchemaProvider.baseSchemaForCapabilities(remoteSessionCapabilities);
final var initRpc = new NetconfDeviceRpc(baseSchema.modelContext(), listener,
new NetconfMessageTransformer(baseSchema.mountPointContext(), false, baseSchema));
- // Acquire schemas
- final var futureSchemas = stateSchemasResolver.resolve(initRpc, remoteSessionCapabilities, id,
- baseSchema.modelContext());
-
- // Convert to sources
- final var sourceResolverFuture = Futures.transform(futureSchemas, availableSchemas -> {
- final var providedSources = availableSchemas.getAvailableYangSchemasQNames();
- LOG.debug("{}: Schemas exposed by ietf-netconf-monitoring: {}", id, providedSources);
-
- final var requiredSources = new HashSet<>(remoteSessionCapabilities.moduleBasedCaps().keySet());
- final var requiredSourcesNotProvided = Sets.difference(requiredSources, providedSources);
- if (!requiredSourcesNotProvided.isEmpty()) {
- LOG.warn("{}: Netconf device does not provide all yang models reported in hello message capabilities,"
- + " required but not provided: {}", id, requiredSourcesNotProvided);
- LOG.warn("{}: Attempting to build schema context from required sources", id);
- }
-
- // 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 var providedSourcesNotRequired = Sets.difference(providedSources, requiredSources);
- if (!providedSourcesNotRequired.isEmpty()) {
- LOG.warn("{}: Netconf device provides additional yang models not reported in "
- + "hello message capabilities: {}", id, providedSourcesNotRequired);
- LOG.warn("{}: Adding provided but not required sources as required to prevent failures", id);
- LOG.debug("{}: Netconf device reported in hello: {}", id, requiredSources);
- requiredSources.addAll(providedSourcesNotRequired);
- }
-
- final var sourceProvider = availableSchemas instanceof LibraryModulesSchemas libraryModule
- ? new LibrarySchemaSourceProvider(id, libraryModule.getAvailableModels())
- : new MonitoringSchemaSourceProvider(id, initRpc.domRpcService());
- return new DeviceSources(requiredSources, providedSources, sourceProvider);
- }, MoreExecutors.directExecutor());
-
- if (shouldListenOnSchemaChange(remoteSessionCapabilities)) {
- registerToBaseNetconfStream(initRpc, listener);
- }
-
- // Set up the EffectiveModelContext for the device
- final var futureSchema = Futures.transformAsync(sourceResolverFuture,
- deviceSources -> assembleSchemaContext(deviceSources, remoteSessionCapabilities), processingExecutor);
+ final var deviceSchema = deviceSchemaProvider.deviceNetconfSchemaFor(id, remoteSessionCapabilities, initRpc,
+ baseSchema, processingExecutor);
// Potentially acquire mount point list and interpret it
- final var netconfDeviceSchemaFuture = Futures.transformAsync(futureSchema,
+ final var netconfDeviceSchemaFuture = Futures.transformAsync(deviceSchema,
result -> Futures.transform(createMountPointContext(result.modelContext(), baseSchema, listener),
mount -> new NetconfDeviceSchema(result.capabilities(), mount), processingExecutor),
processingExecutor);
- schemaFuturesList = Futures.allAsList(sourceResolverFuture, futureSchema, netconfDeviceSchemaFuture);
+ schemaFuture = netconfDeviceSchemaFuture;
Futures.addCallback(netconfDeviceSchemaFuture, new FutureCallback<>() {
- @Override
- public void onSuccess(final NetconfDeviceSchema result) {
- handleSalInitializationSuccess(listener, baseSchema, result, remoteSessionCapabilities,
- getDeviceSpecificRpc(result.mountContext(), listener, baseSchema));
- }
+ @Override
+ public void onSuccess(final NetconfDeviceSchema result) {
+ handleSalInitializationSuccess(listener, baseSchema, result, remoteSessionCapabilities,
+ getDeviceSpecificRpc(result.mountContext(), listener, baseSchema));
+ }
- @Override
- public void onFailure(final Throwable cause) {
- // The method onRemoteSessionDown was called while the EffectiveModelContext for the device
- // was being processed.
- if (cause instanceof CancellationException) {
- LOG.warn("{}: Device communicator was tear down since the schema setup started", id);
- } else {
- handleSalInitializationFailure(listener, cause);
- }
+ @Override
+ public void onFailure(final Throwable cause) {
+ // The method onRemoteSessionDown was called while the EffectiveModelContext for the device was being
+ // processed.
+ if (cause instanceof CancellationException) {
+ LOG.warn("{}: Device communicator was tear down since the schema setup started", id);
+ } else {
+ handleSalInitializationFailure(listener, cause);
}
- }, MoreExecutors.directExecutor());
+ }
+ }, MoreExecutors.directExecutor());
}
- private void registerToBaseNetconfStream(final NetconfDeviceRpc deviceRpc,
+ private void registerToBaseNetconfStream(final NetconfRpcService deviceRpc,
final NetconfDeviceCommunicator listener) {
// 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 var rpcResultListenableFuture = deviceRpc.domRpcService()
- .invokeRpc(CreateSubscription.QNAME, ImmutableNodes.newContainerBuilder()
+ final var rpcResultListenableFuture = deviceRpc.invokeNetconf(CreateSubscription.QNAME,
+ ImmutableNodes.newContainerBuilder()
.withNodeIdentifier(NodeIdentifier.create(CreateSubscriptionInput.QNAME))
// Note: default 'stream' is 'NETCONF', we do not need to create an explicit leaf
.build());
return remoteSessionCapabilities.isNotificationsSupported() && reconnectOnSchemasChange;
}
- private synchronized void handleSalInitializationSuccess(final RemoteDeviceCommunicator listener,
+ private synchronized void handleSalInitializationSuccess(final NetconfDeviceCommunicator listener,
final BaseNetconfSchema baseSchema, final NetconfDeviceSchema deviceSchema,
final NetconfSessionPreferences remoteSessionCapabilities, final Rpcs deviceRpc) {
// NetconfDevice.SchemaSetup can complete after NetconfDeviceCommunicator was closed. In that case do nothing,
return;
}
+ if (shouldListenOnSchemaChange(remoteSessionCapabilities)) {
+ registerToBaseNetconfStream(deviceRpc, listener);
+ }
+
final var messageTransformer = new NetconfMessageTransformer(deviceSchema.mountContext(), true, baseSchema);
// Order is important here: salFacade has to see the device come up and then the notificationHandler can deliver
private synchronized void cleanupInitialization() {
connected = false;
- if (schemaFuturesList != null && !schemaFuturesList.isDone()) {
- if (!schemaFuturesList.cancel(true)) {
- LOG.warn("The cleanup of Schema Futures for device {} was unsuccessful.", id);
- }
+ if (schemaFuture != null && !schemaFuture.isDone() && !schemaFuture.cancel(true)) {
+ LOG.warn("The cleanup of Schema Futures for device {} was unsuccessful.", id);
}
notificationHandler.onRemoteSchemaDown();
- sourceRegistrations.forEach(Registration::close);
- sourceRegistrations.clear();
- }
-
- private synchronized void setConnected(final boolean connected) {
- this.connected = connected;
- }
-
- private ListenableFuture<SchemaResult> assembleSchemaContext(final DeviceSources deviceSources,
- final NetconfSessionPreferences remoteSessionCapabilities) {
- LOG.debug("{}: Resolved device sources to {}", id, deviceSources);
-
- sourceRegistrations.addAll(deviceSources.register(schemaRegistry));
-
- return new SchemaSetup(deviceSources, remoteSessionCapabilities).startResolution();
}
private ListenableFuture<@NonNull MountPointContext> createMountPointContext(
final EffectiveModelContext schemaContext, final BaseNetconfSchema baseSchema,
final NetconfDeviceCommunicator listener) {
- final MountPointContext emptyContext = MountPointContext.of(schemaContext);
+ final var emptyContext = MountPointContext.of(schemaContext);
if (schemaContext.findModule(SchemaMountConstants.RFC8528_MODULE).isEmpty()) {
return Futures.immediateFuture(emptyContext);
}
new NetconfMessageTransformer(result, true, schema));
}
- /**
- * Just a transfer object containing schema related dependencies. Injected in constructor.
- */
- public static class SchemaResourcesDTO {
- private final SchemaSourceRegistry schemaRegistry;
- private final SchemaRepository schemaRepository;
- private final EffectiveModelContextFactory schemaContextFactory;
- private final NetconfDeviceSchemasResolver stateSchemasResolver;
-
- public SchemaResourcesDTO(final SchemaSourceRegistry schemaRegistry,
- final SchemaRepository schemaRepository,
- final EffectiveModelContextFactory schemaContextFactory,
- final NetconfDeviceSchemasResolver deviceSchemasResolver) {
- this.schemaRegistry = requireNonNull(schemaRegistry);
- this.schemaRepository = requireNonNull(schemaRepository);
- this.schemaContextFactory = requireNonNull(schemaContextFactory);
- stateSchemasResolver = requireNonNull(deviceSchemasResolver);
- }
-
- public SchemaSourceRegistry getSchemaRegistry() {
- return schemaRegistry;
- }
-
- public SchemaRepository getSchemaRepository() {
- return schemaRepository;
- }
-
- public EffectiveModelContextFactory getSchemaContextFactory() {
- return schemaContextFactory;
- }
-
- public NetconfDeviceSchemasResolver getStateSchemasResolver() {
- return stateSchemasResolver;
- }
- }
-
/**
* A dedicated exception to indicate when we fail to setup an {@link EffectiveModelContext}.
*/
public static final class EmptySchemaContextException extends Exception {
- @Serial
+ @java.io.Serial
private static final long serialVersionUID = 1L;
public EmptySchemaContextException(final String message) {
super(message);
}
}
-
- /**
- * {@link NetconfDeviceCapabilities} and {@link EffectiveModelContext}.
- */
- private record SchemaResult(
- @NonNull NetconfDeviceCapabilities capabilities,
- @NonNull EffectiveModelContext modelContext) {
-
- SchemaResult {
- requireNonNull(capabilities);
- requireNonNull(modelContext);
- }
- }
-
- /**
- * Schema builder that tries to build schema context from provided sources or biggest subset of it.
- */
- private final class SchemaSetup implements FutureCallback<EffectiveModelContext> {
- private final SettableFuture<SchemaResult> resultFuture = SettableFuture.create();
-
- private final Set<AvailableCapability> nonModuleBasedCapabilities = new HashSet<>();
- private final Map<QName, FailureReason> unresolvedCapabilites = new HashMap<>();
- private final Set<AvailableCapability> resolvedCapabilities = new HashSet<>();
-
- private final DeviceSources deviceSources;
- private final NetconfSessionPreferences remoteSessionCapabilities;
-
- private Collection<SourceIdentifier> requiredSources;
-
- SchemaSetup(final DeviceSources deviceSources, final NetconfSessionPreferences remoteSessionCapabilities) {
- this.deviceSources = deviceSources;
- this.remoteSessionCapabilities = remoteSessionCapabilities;
-
- // If device supports notifications and does not contain necessary modules, add them automatically
- if (remoteSessionCapabilities.containsNonModuleCapability(CapabilityURN.NOTIFICATION)) {
- // FIXME: mutable collection modification!
- deviceSources.getRequiredSourcesQName().addAll(List.of(
- org.opendaylight.yang.svc.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714
- .YangModuleInfoImpl.getInstance().getName(),
- org.opendaylight.yang.svc.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715
- .YangModuleInfoImpl.getInstance().getName())
- );
- }
-
- requiredSources = deviceSources.getRequiredSources();
- final Collection<SourceIdentifier> missingSources = filterMissingSources(requiredSources);
-
- addUnresolvedCapabilities(getQNameFromSourceIdentifiers(missingSources),
- UnavailableCapability.FailureReason.MissingSource);
- requiredSources.removeAll(missingSources);
- }
-
- ListenableFuture<SchemaResult> startResolution() {
- trySetupSchema();
- return resultFuture;
- }
-
- @Override
- public void onSuccess(final EffectiveModelContext result) {
- LOG.debug("{}: Schema context built successfully from {}", id, requiredSources);
-
- final Collection<QName> filteredQNames = Sets.difference(deviceSources.getRequiredSourcesQName(),
- unresolvedCapabilites.keySet());
- resolvedCapabilities.addAll(filteredQNames.stream()
- .map(capability -> new AvailableCapabilityBuilder()
- .setCapability(capability.toString())
- .setCapabilityOrigin(remoteSessionCapabilities.capabilityOrigin(capability))
- .build())
- .collect(Collectors.toList()));
-
- nonModuleBasedCapabilities.addAll(remoteSessionCapabilities.nonModuleCaps().keySet().stream()
- .map(capability -> new AvailableCapabilityBuilder()
- .setCapability(capability)
- .setCapabilityOrigin(remoteSessionCapabilities.capabilityOrigin(capability))
- .build())
- .collect(Collectors.toList()));
-
-
- resultFuture.set(new SchemaResult(new NetconfDeviceCapabilities(ImmutableMap.copyOf(unresolvedCapabilites),
- ImmutableSet.copyOf(resolvedCapabilities), ImmutableSet.copyOf(nonModuleBasedCapabilities)), result));
- }
-
- @Override
- public void onFailure(final Throwable cause) {
- // schemaBuilderFuture.checkedGet() throws only SchemaResolutionException
- // that might be wrapping a MissingSchemaSourceException so we need to look
- // at the cause of the exception to make sure we don't misinterpret it.
- if (cause instanceof MissingSchemaSourceException) {
- requiredSources = handleMissingSchemaSourceException((MissingSchemaSourceException) cause);
- } else if (cause instanceof SchemaResolutionException) {
- requiredSources = handleSchemaResolutionException((SchemaResolutionException) cause);
- } else {
- LOG.debug("Unhandled failure", cause);
- resultFuture.setException(cause);
- // No more trying...
- return;
- }
-
- trySetupSchema();
- }
-
- private void trySetupSchema() {
- if (!requiredSources.isEmpty()) {
- // Initiate async resolution, drive it back based on the result
- LOG.trace("{}: Trying to build schema context from {}", id, requiredSources);
- Futures.addCallback(schemaContextFactory.createEffectiveModelContext(requiredSources), this,
- MoreExecutors.directExecutor());
- } else {
- LOG.debug("{}: no more sources for schema context", id);
- resultFuture.setException(new EmptySchemaContextException(id + ": No more sources for schema context"));
- }
- }
-
- private List<SourceIdentifier> filterMissingSources(final Collection<SourceIdentifier> origSources) {
- return origSources.parallelStream().filter(sourceIdentifier -> {
- try {
- schemaRepository.getSchemaSource(sourceIdentifier, YangTextSource.class).get();
- return false;
- } catch (InterruptedException | ExecutionException e) {
- return true;
- }
- }).collect(Collectors.toList());
- }
-
- private void addUnresolvedCapabilities(final Collection<QName> capabilities, final FailureReason reason) {
- for (QName s : capabilities) {
- unresolvedCapabilites.put(s, reason);
- }
- }
-
- private List<SourceIdentifier> handleMissingSchemaSourceException(
- final MissingSchemaSourceException exception) {
- // In case source missing, try without it
- final SourceIdentifier missingSource = exception.sourceId();
- LOG.warn("{}: Unable to build schema context, missing source {}, will reattempt without it",
- id, missingSource);
- LOG.debug("{}: Unable to build schema context, missing source {}, will reattempt without it",
- id, missingSource, exception);
- final var qNameOfMissingSource = getQNameFromSourceIdentifiers(Sets.newHashSet(missingSource));
- if (!qNameOfMissingSource.isEmpty()) {
- addUnresolvedCapabilities(qNameOfMissingSource, UnavailableCapability.FailureReason.MissingSource);
- }
- return stripUnavailableSource(missingSource);
- }
-
- private Collection<SourceIdentifier> handleSchemaResolutionException(
- final SchemaResolutionException resolutionException) {
- // In case resolution error, try only with resolved sources
- // There are two options why schema resolution exception occurred : unsatisfied imports or flawed model
- // FIXME Do we really have assurance that these two cases cannot happen at once?
- final var failedSourceId = resolutionException.sourceId();
- if (failedSourceId != null) {
- // flawed model - exclude it
- LOG.warn("{}: Unable to build schema context, failed to resolve source {}, will reattempt without it",
- id, failedSourceId);
- LOG.warn("{}: Unable to build schema context, failed to resolve source {}, will reattempt without it",
- id, failedSourceId, resolutionException);
- addUnresolvedCapabilities(getQNameFromSourceIdentifiers(List.of(failedSourceId)),
- UnavailableCapability.FailureReason.UnableToResolve);
- return stripUnavailableSource(failedSourceId);
- }
- // unsatisfied imports
- addUnresolvedCapabilities(
- getQNameFromSourceIdentifiers(resolutionException.getUnsatisfiedImports().keySet()),
- UnavailableCapability.FailureReason.UnableToResolve);
- LOG.warn("{}: Unable to build schema context, unsatisfied imports {}, will reattempt with resolved only",
- id, resolutionException.getUnsatisfiedImports());
- LOG.debug("{}: Unable to build schema context, unsatisfied imports {}, will reattempt with resolved only",
- id, resolutionException.getUnsatisfiedImports(), resolutionException);
- return resolutionException.getResolvedSources();
- }
-
- private List<SourceIdentifier> stripUnavailableSource(final SourceIdentifier sourceIdToRemove) {
- final var tmp = new ArrayList<>(requiredSources);
- checkState(tmp.remove(sourceIdToRemove), "%s: Trying to remove %s from %s failed", id, sourceIdToRemove,
- requiredSources);
- return tmp;
- }
-
- private Collection<QName> getQNameFromSourceIdentifiers(final Collection<SourceIdentifier> identifiers) {
- final Collection<QName> qNames = Collections2.transform(identifiers, this::getQNameFromSourceIdentifier);
-
- if (qNames.isEmpty()) {
- LOG.debug("{}: Unable to map any source identifiers to a capability reported by device : {}", id,
- identifiers);
- }
- return Collections2.filter(qNames, Predicates.notNull());
- }
-
- 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.name().getLocalName())) {
- continue;
- }
-
- if (Objects.equals(identifier.revision(), qname.getRevision().orElse(null))) {
- return qname;
- }
- }
- LOG.warn("Unable to map identifier to a devices reported capability: {} Available: {}",identifier,
- deviceSources.getRequiredSourcesQName());
- // return null since we cannot find the QName,
- // this capability will be removed from required sources and not reported as unresolved-capability
- return null;
- }
- }
}
import static java.util.Objects.requireNonNull;
import java.util.concurrent.Executor;
-import org.opendaylight.netconf.client.mdsal.NetconfDevice.SchemaResourcesDTO;
+import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchemaProvider;
import org.opendaylight.netconf.client.mdsal.api.DeviceActionFactory;
+import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchemaProvider;
import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceHandler;
import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
-public class NetconfDeviceBuilder {
+// FIXME: everything except DeviceActionFactory is mandatory, hence this builder is a superfluous indirection
+public final class NetconfDeviceBuilder {
private boolean reconnectOnSchemasChange;
- private SchemaResourcesDTO schemaResourcesDTO;
+ private DeviceNetconfSchemaProvider deviceSchemaProvider;
private RemoteDeviceId id;
private RemoteDeviceHandler salFacade;
- private Executor globalProcessingExecutor;
+ // FIXME: this should not be here
+ private Executor processingExecutor;
private DeviceActionFactory deviceActionFactory;
- private BaseNetconfSchemaProvider baseSchemas;
+ private BaseNetconfSchemaProvider baseSchemaProvider;
- public NetconfDeviceBuilder setReconnectOnSchemasChange(final boolean reconnectOnSchemasChange) {
+ public @NonNull NetconfDeviceBuilder setReconnectOnSchemasChange(final boolean reconnectOnSchemasChange) {
this.reconnectOnSchemasChange = reconnectOnSchemasChange;
return this;
}
- public NetconfDeviceBuilder setId(final RemoteDeviceId id) {
- this.id = id;
+ public @NonNull NetconfDeviceBuilder setId(final RemoteDeviceId id) {
+ this.id = requireNonNull(id);
return this;
}
- public NetconfDeviceBuilder setSchemaResourcesDTO(final SchemaResourcesDTO schemaResourcesDTO) {
- this.schemaResourcesDTO = schemaResourcesDTO;
+ public @NonNull NetconfDeviceBuilder setSalFacade(final RemoteDeviceHandler salFacade) {
+ this.salFacade = requireNonNull(salFacade);
return this;
}
- public NetconfDeviceBuilder setSalFacade(final RemoteDeviceHandler salFacade) {
- this.salFacade = salFacade;
+ public @NonNull NetconfDeviceBuilder setProcessingExecutor(final Executor processingExecutor) {
+ this.processingExecutor = requireNonNull(processingExecutor);
return this;
}
- public NetconfDeviceBuilder setGlobalProcessingExecutor(final Executor globalProcessingExecutor) {
- this.globalProcessingExecutor = globalProcessingExecutor;
- return this;
- }
-
- public NetconfDeviceBuilder setDeviceActionFactory(final DeviceActionFactory deviceActionFactory) {
+ public @NonNull NetconfDeviceBuilder setDeviceActionFactory(final DeviceActionFactory deviceActionFactory) {
this.deviceActionFactory = deviceActionFactory;
return this;
}
- public NetconfDeviceBuilder setBaseSchemas(final BaseNetconfSchemaProvider baseSchemas) {
- this.baseSchemas = requireNonNull(baseSchemas);
+ public @NonNull NetconfDeviceBuilder setBaseSchemaProvider(final BaseNetconfSchemaProvider baseSchemaProvider) {
+ this.baseSchemaProvider = requireNonNull(baseSchemaProvider);
return this;
}
- public NetconfDevice build() {
- validation();
- return new NetconfDevice(schemaResourcesDTO, baseSchemas, id, salFacade,
- globalProcessingExecutor, reconnectOnSchemasChange, deviceActionFactory);
+ public @NonNull NetconfDeviceBuilder setDeviceSchemaProvider(
+ final DeviceNetconfSchemaProvider deviceSchemaProvider) {
+ this.deviceSchemaProvider = requireNonNull(deviceSchemaProvider);
+ return this;
}
- private void validation() {
- requireNonNull(baseSchemas, "BaseSchemas is not initialized");
- requireNonNull(id, "RemoteDeviceId is not initialized");
- requireNonNull(salFacade, "RemoteDeviceHandler is not initialized");
- requireNonNull(globalProcessingExecutor, "ExecutorService is not initialized");
- requireNonNull(schemaResourcesDTO, "SchemaResourceDTO is not initialized");
+ public @NonNull NetconfDevice build() {
+ return new NetconfDevice(
+ requireNonNull(id, "RemoteDeviceId is not initialized"),
+ requireNonNull(baseSchemaProvider, "BaseNetconfSchemaProvider is not initialized"),
+ requireNonNull(deviceSchemaProvider, "DeviceNetconfSchemaProvider is not initialized"),
+ requireNonNull(salFacade, "RemoteDeviceHandler is not initialized"),
+ requireNonNull(processingExecutor, "Executor is not initialized"),
+ reconnectOnSchemasChange, deviceActionFactory);
}
}
import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.yangtools.yang.data.api.schema.MountPointContext;
-import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
/**
- * {@link NetconfDeviceCapabilities} and {@link EffectiveModelContext} pertaining to a {@link NetconfDevice}.
+ * {@link NetconfDeviceCapabilities} and {@link MountPointContext} pertaining to a {@link NetconfDevice}.
*/
public record NetconfDeviceSchema(
@NonNull NetconfDeviceCapabilities capabilities,
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.mdsal.dom.api.DOMRpcResult;
-import org.opendaylight.mdsal.dom.api.DOMRpcService;
import org.opendaylight.netconf.api.NamespaceURN;
import org.opendaylight.netconf.api.xml.XmlUtil;
import org.opendaylight.netconf.client.mdsal.api.NetconfDeviceSchemas;
+import org.opendaylight.netconf.client.mdsal.api.NetconfRpcService;
import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
import org.opendaylight.netconf.common.mdsal.NormalizedDataUtil;
/**
* Issue get request to remote device and parse response to find all schemas under netconf-state/schemas.
*/
- static ListenableFuture<NetconfStateSchemas> forDevice(final DOMRpcService deviceRpc,
+ static ListenableFuture<NetconfStateSchemas> forDevice(final NetconfRpcService deviceRpc,
final NetconfSessionPreferences remoteSessionCapabilities, final RemoteDeviceId id,
final EffectiveModelContext modelContext) {
if (!remoteSessionCapabilities.isMonitoringSupported()) {
}
final var future = SettableFuture.<NetconfStateSchemas>create();
- Futures.addCallback(deviceRpc.invokeRpc(Get.QNAME, GET_SCHEMAS_RPC),
+ Futures.addCallback(deviceRpc.invokeNetconf(Get.QNAME, GET_SCHEMAS_RPC),
new FutureCallback<DOMRpcResult>() {
@Override
public void onSuccess(final DOMRpcResult result) {
import com.google.common.util.concurrent.ListenableFuture;
import org.opendaylight.netconf.client.mdsal.api.NetconfDeviceSchemas;
import org.opendaylight.netconf.client.mdsal.api.NetconfDeviceSchemasResolver;
+import org.opendaylight.netconf.client.mdsal.api.NetconfRpcService;
import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
-import org.opendaylight.netconf.client.mdsal.spi.NetconfDeviceRpc;
import org.opendaylight.yang.svc.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.YangModuleInfoImpl;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.QNameModule;
.bindTo(QNameModule.create(RFC8525_YANG_LIBRARY_CAPABILITY.getNamespace(), Revision.of("2016-06-21"))).intern();
@Override
- public ListenableFuture<? extends NetconfDeviceSchemas> resolve(final NetconfDeviceRpc deviceRpc,
+ public ListenableFuture<? extends NetconfDeviceSchemas> resolve(final NetconfRpcService deviceRpc,
final NetconfSessionPreferences remoteSessionCapabilities, final RemoteDeviceId id,
final EffectiveModelContext schemaContext) {
// FIXME: I think we should prefer YANG library here
if (remoteSessionCapabilities.isMonitoringSupported()) {
- return NetconfStateSchemas.forDevice(deviceRpc.domRpcService(), remoteSessionCapabilities, id,
- schemaContext);
+ return NetconfStateSchemas.forDevice(deviceRpc, remoteSessionCapabilities, id, schemaContext);
}
if (remoteSessionCapabilities.containsModuleCapability(RFC8525_YANG_LIBRARY_CAPABILITY)
|| remoteSessionCapabilities.containsModuleCapability(RFC7895_YANG_LIBRARY_CAPABILITY)) {
* @return A {@link BaseNetconfSchema}
* @throws NullPointerException if {@code capabilityURNs} is {@code null}
*/
+ // FIXME: return ListenableFuture
BaseNetconfSchema baseSchemaForCapabilities(NetconfSessionPreferences sessionPreferences);
}
--- /dev/null
+/*
+ * Copyright (c) 2024 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.client.mdsal.api;
+
+import static java.util.Objects.requireNonNull;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.netconf.client.mdsal.NetconfDeviceCapabilities;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+
+/**
+ * {@link NetconfDeviceCapabilities} and {@link EffectiveModelContext}.
+ */
+@NonNullByDefault
+public record DeviceNetconfSchema(NetconfDeviceCapabilities capabilities, EffectiveModelContext modelContext) {
+ public DeviceNetconfSchema {
+ requireNonNull(capabilities);
+ requireNonNull(modelContext);
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2024 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.client.mdsal.api;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import java.util.concurrent.Executor;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.yangtools.yang.model.repo.api.EffectiveModelContextFactory;
+import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository;
+import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
+
+/**
+ * A provider of {@link DeviceNetconfSchema}.
+ */
+@NonNullByDefault
+public interface DeviceNetconfSchemaProvider {
+
+ ListenableFuture<DeviceNetconfSchema> deviceNetconfSchemaFor(RemoteDeviceId deviceId,
+ NetconfSessionPreferences sessionPreferences, NetconfRpcService deviceRpc, BaseNetconfSchema baseSchema,
+ // FIXME: this parameter should not be here
+ Executor processingExecutor);
+
+ // FIXME: These support:
+ // - external URL-based pre-registration of schema sources from topology, which should really be catered
+ // through deviceNetconfSchemaFor() with the sources being registered only for the duration of schema
+ // assembly
+ // - netconf-topology-singleton lifecycle, which needs to be carefully examined
+ @Deprecated
+ SchemaRepository repository();
+
+ @Deprecated
+ SchemaSourceRegistry registry();
+
+ @Deprecated
+ EffectiveModelContextFactory contextFactory();
+}
package org.opendaylight.netconf.client.mdsal.api;
import com.google.common.util.concurrent.ListenableFuture;
-import org.opendaylight.netconf.client.mdsal.spi.NetconfDeviceRpc;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
/**
*/
public interface NetconfDeviceSchemasResolver {
// FIXME: document this method
- ListenableFuture<? extends NetconfDeviceSchemas> resolve(NetconfDeviceRpc deviceRpc,
+ ListenableFuture<? extends NetconfDeviceSchemas> resolve(NetconfRpcService deviceRpc,
NetconfSessionPreferences remoteSessionCapabilities, RemoteDeviceId id, EffectiveModelContext schemaContext);
}
import com.google.common.annotations.Beta;
import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.opendaylight.netconf.client.mdsal.NetconfDevice.SchemaResourcesDTO;
@Beta
@NonNullByDefault
public interface SchemaResourceManager {
-
// FIXME: document this, nodeId is not quite appropriate name here: it should be a @NonNull id with .toString()
// being interesting
- // FIXME: subDirectory should have be really String..., placing the onus of splitting the directory to callers,
- // so we do not get separator ambiguity
- SchemaResourcesDTO getSchemaResources(String subDirectory, Object nodeId);
+ // FIXME: subDirectory should have be really 'String...' or a 'java.nio.file.Path', placing the onus of splitting
+ // the directory to callers, so we do not get separator ambiguity
+ DeviceNetconfSchemaProvider getSchemaResources(String subDirectory, Object nodeId);
}
--- /dev/null
+/*
+ * Copyright (c) 2024 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.client.mdsal.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+import java.util.HashSet;
+import java.util.concurrent.Executor;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.netconf.client.mdsal.LibraryModulesSchemas;
+import org.opendaylight.netconf.client.mdsal.LibrarySchemaSourceProvider;
+import org.opendaylight.netconf.client.mdsal.MonitoringSchemaSourceProvider;
+import org.opendaylight.netconf.client.mdsal.NetconfStateSchemasResolverImpl;
+import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchema;
+import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchema;
+import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchemaProvider;
+import org.opendaylight.netconf.client.mdsal.api.NetconfDeviceSchemasResolver;
+import org.opendaylight.netconf.client.mdsal.api.NetconfRpcService;
+import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
+import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
+import org.opendaylight.yangtools.concepts.Registration;
+import org.opendaylight.yangtools.yang.model.repo.api.EffectiveModelContextFactory;
+import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactoryConfiguration;
+import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository;
+import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
+import org.opendaylight.yangtools.yang.parser.repo.SharedSchemaRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@VisibleForTesting
+public final class DefaultDeviceNetconfSchemaProvider implements DeviceNetconfSchemaProvider {
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultDeviceNetconfSchemaProvider.class);
+
+ // FIXME: resolver seems to be a useless indirection
+ private final NetconfDeviceSchemasResolver resolver;
+ private final @NonNull EffectiveModelContextFactory contextFactory;
+ private final @NonNull SchemaSourceRegistry registry;
+ private final @NonNull SchemaRepository repository;
+ // FIXME: private final Executor processingExecutor;
+
+ DefaultDeviceNetconfSchemaProvider(final SharedSchemaRepository repository) {
+ this(repository, repository,
+ repository.createEffectiveModelContextFactory(SchemaContextFactoryConfiguration.getDefault()),
+ new NetconfStateSchemasResolverImpl());
+ }
+
+ @VisibleForTesting
+ public DefaultDeviceNetconfSchemaProvider(final SchemaSourceRegistry registry, final SchemaRepository repository,
+ final EffectiveModelContextFactory contextFactory, final NetconfDeviceSchemasResolver resolver) {
+ this.registry = requireNonNull(registry);
+ this.repository = requireNonNull(repository);
+ this.contextFactory = requireNonNull(contextFactory);
+ this.resolver = requireNonNull(resolver);
+ }
+
+ @Override
+ public ListenableFuture<DeviceNetconfSchema> deviceNetconfSchemaFor(final RemoteDeviceId deviceId,
+ final NetconfSessionPreferences sessionPreferences, final NetconfRpcService deviceRpc,
+ final BaseNetconfSchema baseSchema, final Executor processingExecutor) {
+
+ // Acquire schemas
+ final var futureSchemas = resolver.resolve(deviceRpc, sessionPreferences, deviceId, baseSchema.modelContext());
+
+ // Convert to sources
+ final var sourceResolverFuture = Futures.transform(futureSchemas, availableSchemas -> {
+ final var providedSources = availableSchemas.getAvailableYangSchemasQNames();
+ LOG.debug("{}: Schemas exposed by ietf-netconf-monitoring: {}", deviceId, providedSources);
+
+ final var requiredSources = new HashSet<>(sessionPreferences.moduleBasedCaps().keySet());
+ final var requiredSourcesNotProvided = Sets.difference(requiredSources, providedSources);
+ if (!requiredSourcesNotProvided.isEmpty()) {
+ LOG.warn("{}: Netconf device does not provide all yang models reported in hello message capabilities,"
+ + " required but not provided: {}", deviceId, requiredSourcesNotProvided);
+ LOG.warn("{}: Attempting to build schema context from required sources", deviceId);
+ }
+
+ // 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 var providedSourcesNotRequired = Sets.difference(providedSources, requiredSources);
+ if (!providedSourcesNotRequired.isEmpty()) {
+ LOG.warn("{}: Netconf device provides additional yang models not reported in "
+ + "hello message capabilities: {}", deviceId, providedSourcesNotRequired);
+ LOG.warn("{}: Adding provided but not required sources as required to prevent failures", deviceId);
+ LOG.debug("{}: Netconf device reported in hello: {}", deviceId, requiredSources);
+ requiredSources.addAll(providedSourcesNotRequired);
+ }
+
+ // FIXME: this instanceof check is quite bad
+ final var sourceProvider = availableSchemas instanceof LibraryModulesSchemas libraryModule
+ ? new LibrarySchemaSourceProvider(deviceId, libraryModule.getAvailableModels())
+ : new MonitoringSchemaSourceProvider(deviceId, deviceRpc);
+ return new DeviceSources(requiredSources, providedSources, sourceProvider);
+ }, MoreExecutors.directExecutor());
+
+ // Set up the EffectiveModelContext for the device
+ return Futures.transformAsync(sourceResolverFuture, deviceSources -> {
+ LOG.debug("{}: Resolved device sources to {}", deviceId, deviceSources);
+
+ // Register all sources with repository and start resolution
+ final var registrations = deviceSources.register(registry);
+ final var future = new SchemaSetup(repository, contextFactory, deviceId, deviceSources, sessionPreferences)
+ .startResolution();
+
+ // Unregister sources once resolution is complete
+ future.addListener(() -> registrations.forEach(Registration::close), processingExecutor);
+
+ return future;
+ }, processingExecutor);
+ }
+
+ @Deprecated
+ @Override
+ public SchemaRepository repository() {
+ return repository;
+ }
+
+ @Deprecated
+ @Override
+ public SchemaSourceRegistry registry() {
+ return registry;
+ }
+
+ @Deprecated
+ @Override
+ public EffectiveModelContextFactory contextFactory() {
+ return contextFactory;
+ }
+}
import javax.inject.Singleton;
import org.checkerframework.checker.lock.qual.GuardedBy;
import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.netconf.client.mdsal.NetconfDevice.SchemaResourcesDTO;
-import org.opendaylight.netconf.client.mdsal.NetconfStateSchemasResolverImpl;
+import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchemaProvider;
import org.opendaylight.netconf.client.mdsal.api.SchemaResourceManager;
import org.opendaylight.yangtools.yang.model.api.source.YangTextSource;
-import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactoryConfiguration;
import org.opendaylight.yangtools.yang.model.repo.fs.FilesystemSchemaSourceCache;
import org.opendaylight.yangtools.yang.model.repo.spi.SoftSchemaSourceCache;
import org.opendaylight.yangtools.yang.model.spi.source.YangIRSource;
private static final Logger LOG = LoggerFactory.getLogger(DefaultSchemaResourceManager.class);
@GuardedBy("this")
- private final Map<String, SchemaResourcesDTO> resources = new HashMap<>();
- private final @NonNull SchemaResourcesDTO defaultResources;
+ private final Map<String, DeviceNetconfSchemaProvider> resources = new HashMap<>();
+ private final @NonNull DeviceNetconfSchemaProvider defaultResources;
private final YangParserFactory parserFactory;
private final String defaultSubdirectory;
private final String rootDirectory;
}
@Override
- public SchemaResourcesDTO getSchemaResources(final String subdir, final Object nodeId) {
+ public DeviceNetconfSchemaProvider getSchemaResources(final String subdir, final Object nodeId) {
if (defaultSubdirectory.equals(subdir)) {
// Fast path for default devices
return defaultResources;
return getSchemaResources(subdir);
}
- private synchronized @NonNull SchemaResourcesDTO getSchemaResources(final String subdir) {
+ private synchronized @NonNull DeviceNetconfSchemaProvider getSchemaResources(final String subdir) {
// Fast path for unusual devices
- final SchemaResourcesDTO existing = resources.get(subdir);
+ final var existing = resources.get(subdir);
if (existing != null) {
return existing;
}
- final SchemaResourcesDTO created = createResources(subdir);
+ final var created = createResources(subdir);
resources.put(subdir, created);
return created;
}
- private @NonNull SchemaResourcesDTO createResources(final String subdir) {
+ private @NonNull DeviceNetconfSchemaProvider createResources(final String subdir) {
// Setup the baseline empty registry
- final SharedSchemaRepository repository = new SharedSchemaRepository(subdir, parserFactory);
+ final var repository = new SharedSchemaRepository(subdir, parserFactory);
// Teach the registry how to transform YANG text to IRSchemaSource internally
repository.registerSchemaSourceListener(TextToIRTransformer.create(repository, repository));
// Attach a soft cache of IRSchemaSource instances. This is important during convergence when we are fishing
// for a consistent set of modules, as it skips the need to re-parse the text sources multiple times. It also
// helps establishing different sets of contexts, as they can share this pre-made cache.
- repository.registerSchemaSourceListener(
- new SoftSchemaSourceCache<>(repository, YangIRSource.class));
+ repository.registerSchemaSourceListener(new SoftSchemaSourceCache<>(repository, YangIRSource.class));
// Attach the filesystem cache, providing persistence capability, so that restarts do not require us to
// re-populate the cache. This also acts as a side-load capability, as anything pre-populated into that
// directory will not be fetched from the device.
- repository.registerSchemaSourceListener(new FilesystemSchemaSourceCache<>(repository,
- YangTextSource.class, new File(rootDirectory + File.separator + subdir)));
+ repository.registerSchemaSourceListener(new FilesystemSchemaSourceCache<>(repository, YangTextSource.class,
+ new File(rootDirectory + File.separator + subdir)));
- return new SchemaResourcesDTO(repository, repository,
- repository.createEffectiveModelContextFactory(SchemaContextFactoryConfiguration.getDefault()),
- new NetconfStateSchemasResolverImpl());
+ return new DefaultDeviceNetconfSchemaProvider(repository);
}
}
* 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.client.mdsal;
+package org.opendaylight.netconf.client.mdsal.impl;
import static java.util.Objects.requireNonNull;
--- /dev/null
+/*
+ * Copyright (c) 2024 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.client.mdsal.impl;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import org.opendaylight.netconf.api.CapabilityURN;
+import org.opendaylight.netconf.client.mdsal.NetconfDevice.EmptySchemaContextException;
+import org.opendaylight.netconf.client.mdsal.NetconfDeviceCapabilities;
+import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchema;
+import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
+import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.available.capabilities.AvailableCapability;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.available.capabilities.AvailableCapabilityBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.unavailable.capabilities.UnavailableCapability;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.unavailable.capabilities.UnavailableCapability.FailureReason;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.source.YangTextSource;
+import org.opendaylight.yangtools.yang.model.repo.api.EffectiveModelContextFactory;
+import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException;
+import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository;
+import org.opendaylight.yangtools.yang.model.repo.api.SchemaResolutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Schema builder that tries to build schema context from provided sources or biggest subset of it.
+ */
+final class SchemaSetup implements FutureCallback<EffectiveModelContext> {
+ private static final Logger LOG = LoggerFactory.getLogger(SchemaSetup.class);
+
+ private final SettableFuture<DeviceNetconfSchema> resultFuture = SettableFuture.create();
+ private final Set<AvailableCapability> nonModuleBasedCapabilities = new HashSet<>();
+ private final Map<QName, FailureReason> unresolvedCapabilites = new HashMap<>();
+ private final Set<AvailableCapability> resolvedCapabilities = new HashSet<>();
+
+ private final RemoteDeviceId deviceId;
+ private final DeviceSources deviceSources;
+ private final NetconfSessionPreferences remoteSessionCapabilities;
+ private final SchemaRepository repository;
+ private final EffectiveModelContextFactory contextFactory;
+
+ private Collection<SourceIdentifier> requiredSources;
+
+ SchemaSetup(final SchemaRepository repository, final EffectiveModelContextFactory contextFactory,
+ final RemoteDeviceId deviceId, final DeviceSources deviceSources,
+ final NetconfSessionPreferences remoteSessionCapabilities) {
+ this.repository = requireNonNull(repository);
+ this.contextFactory = requireNonNull(contextFactory);
+ this.deviceId = requireNonNull(deviceId);
+ this.deviceSources = requireNonNull(deviceSources);
+ this.remoteSessionCapabilities = requireNonNull(remoteSessionCapabilities);
+
+ // If device supports notifications and does not contain necessary modules, add them automatically
+ if (remoteSessionCapabilities.containsNonModuleCapability(CapabilityURN.NOTIFICATION)) {
+ // FIXME: mutable collection modification!
+ deviceSources.getRequiredSourcesQName().addAll(List.of(
+ org.opendaylight.yang.svc.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714
+ .YangModuleInfoImpl.getInstance().getName(),
+ org.opendaylight.yang.svc.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715
+ .YangModuleInfoImpl.getInstance().getName())
+ );
+ }
+
+ requiredSources = deviceSources.getRequiredSources();
+ final var missingSources = filterMissingSources(requiredSources);
+
+ addUnresolvedCapabilities(getQNameFromSourceIdentifiers(missingSources),
+ UnavailableCapability.FailureReason.MissingSource);
+ requiredSources.removeAll(missingSources);
+ }
+
+ ListenableFuture<DeviceNetconfSchema> startResolution() {
+ trySetupSchema();
+ return resultFuture;
+ }
+
+ @Override
+ public void onSuccess(final EffectiveModelContext result) {
+ LOG.debug("{}: Schema context built successfully from {}", deviceId, requiredSources);
+
+ final Collection<QName> filteredQNames = Sets.difference(deviceSources.getRequiredSourcesQName(),
+ unresolvedCapabilites.keySet());
+ resolvedCapabilities.addAll(filteredQNames.stream()
+ .map(capability -> new AvailableCapabilityBuilder()
+ .setCapability(capability.toString())
+ .setCapabilityOrigin(remoteSessionCapabilities.capabilityOrigin(capability))
+ .build())
+ .collect(Collectors.toList()));
+
+ nonModuleBasedCapabilities.addAll(remoteSessionCapabilities.nonModuleCaps().keySet().stream()
+ .map(capability -> new AvailableCapabilityBuilder()
+ .setCapability(capability)
+ .setCapabilityOrigin(remoteSessionCapabilities.capabilityOrigin(capability))
+ .build())
+ .collect(Collectors.toList()));
+
+
+ resultFuture.set(new DeviceNetconfSchema(new NetconfDeviceCapabilities(
+ ImmutableMap.copyOf(unresolvedCapabilites), ImmutableSet.copyOf(resolvedCapabilities),
+ ImmutableSet.copyOf(nonModuleBasedCapabilities)), result));
+ }
+
+ @Override
+ public void onFailure(final Throwable cause) {
+ // schemaBuilderFuture.checkedGet() throws only SchemaResolutionException
+ // that might be wrapping a MissingSchemaSourceException so we need to look
+ // at the cause of the exception to make sure we don't misinterpret it.
+ if (cause instanceof MissingSchemaSourceException) {
+ requiredSources = handleMissingSchemaSourceException((MissingSchemaSourceException) cause);
+ } else if (cause instanceof SchemaResolutionException) {
+ requiredSources = handleSchemaResolutionException((SchemaResolutionException) cause);
+ } else {
+ LOG.debug("Unhandled failure", cause);
+ resultFuture.setException(cause);
+ // No more trying...
+ return;
+ }
+
+ trySetupSchema();
+ }
+
+ private void trySetupSchema() {
+ if (!requiredSources.isEmpty()) {
+ // Initiate async resolution, drive it back based on the result
+ LOG.trace("{}: Trying to build schema context from {}", deviceId, requiredSources);
+ Futures.addCallback(contextFactory.createEffectiveModelContext(requiredSources), this,
+ MoreExecutors.directExecutor());
+ } else {
+ LOG.debug("{}: no more sources for schema context", deviceId);
+ resultFuture.setException(
+ new EmptySchemaContextException(deviceId + ": No more sources for schema context"));
+ }
+ }
+
+ private List<SourceIdentifier> filterMissingSources(final Collection<SourceIdentifier> origSources) {
+ return origSources.parallelStream()
+ .filter(sourceId -> {
+ try {
+ repository.getSchemaSource(sourceId, YangTextSource.class).get();
+ return false;
+ } catch (InterruptedException | ExecutionException e) {
+ LOG.debug("Failed to acquire source {}", sourceId, e);
+ return true;
+ }
+ })
+ .collect(Collectors.toList());
+ }
+
+ private void addUnresolvedCapabilities(final Collection<QName> capabilities, final FailureReason reason) {
+ for (QName s : capabilities) {
+ unresolvedCapabilites.put(s, reason);
+ }
+ }
+
+ private List<SourceIdentifier> handleMissingSchemaSourceException(
+ final MissingSchemaSourceException exception) {
+ // In case source missing, try without it
+ final SourceIdentifier missingSource = exception.sourceId();
+ LOG.warn("{}: Unable to build schema context, missing source {}, will reattempt without it",
+ deviceId, missingSource);
+ LOG.debug("{}: Unable to build schema context, missing source {}, will reattempt without it",
+ deviceId, missingSource, exception);
+ final var qNameOfMissingSource = getQNameFromSourceIdentifiers(Sets.newHashSet(missingSource));
+ if (!qNameOfMissingSource.isEmpty()) {
+ addUnresolvedCapabilities(qNameOfMissingSource, UnavailableCapability.FailureReason.MissingSource);
+ }
+ return stripUnavailableSource(missingSource);
+ }
+
+ private Collection<SourceIdentifier> handleSchemaResolutionException(
+ final SchemaResolutionException resolutionException) {
+ // In case resolution error, try only with resolved sources
+ // There are two options why schema resolution exception occurred : unsatisfied imports or flawed model
+ // FIXME Do we really have assurance that these two cases cannot happen at once?
+ final var failedSourceId = resolutionException.sourceId();
+ if (failedSourceId != null) {
+ // flawed model - exclude it
+ LOG.warn("{}: Unable to build schema context, failed to resolve source {}, will reattempt without it",
+ deviceId, failedSourceId);
+ LOG.warn("{}: Unable to build schema context, failed to resolve source {}, will reattempt without it",
+ deviceId, failedSourceId, resolutionException);
+ addUnresolvedCapabilities(getQNameFromSourceIdentifiers(List.of(failedSourceId)),
+ UnavailableCapability.FailureReason.UnableToResolve);
+ return stripUnavailableSource(failedSourceId);
+ }
+ // unsatisfied imports
+ addUnresolvedCapabilities(
+ getQNameFromSourceIdentifiers(resolutionException.getUnsatisfiedImports().keySet()),
+ UnavailableCapability.FailureReason.UnableToResolve);
+ LOG.warn("{}: Unable to build schema context, unsatisfied imports {}, will reattempt with resolved only",
+ deviceId, resolutionException.getUnsatisfiedImports());
+ LOG.debug("{}: Unable to build schema context, unsatisfied imports {}, will reattempt with resolved only",
+ deviceId, resolutionException.getUnsatisfiedImports(), resolutionException);
+ return resolutionException.getResolvedSources();
+ }
+
+ private List<SourceIdentifier> stripUnavailableSource(final SourceIdentifier sourceIdToRemove) {
+ final var tmp = new ArrayList<>(requiredSources);
+ checkState(tmp.remove(sourceIdToRemove), "%s: Trying to remove %s from %s failed", deviceId, sourceIdToRemove,
+ requiredSources);
+ return tmp;
+ }
+
+ private Collection<QName> getQNameFromSourceIdentifiers(final Collection<SourceIdentifier> identifiers) {
+ final Collection<QName> qNames = Collections2.transform(identifiers, this::getQNameFromSourceIdentifier);
+
+ if (qNames.isEmpty()) {
+ LOG.debug("{}: Unable to map any source identifiers to a capability reported by device : {}", deviceId,
+ identifiers);
+ }
+ return Collections2.filter(qNames, Predicates.notNull());
+ }
+
+ 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.name().getLocalName())) {
+ continue;
+ }
+
+ if (Objects.equals(identifier.revision(), qname.getRevision().orElse(null))) {
+ return qname;
+ }
+ }
+ LOG.warn("Unable to map identifier to a devices reported capability: {} Available: {}",identifier,
+ deviceSources.getRequiredSourcesQName());
+ // return null since we cannot find the QName,
+ // this capability will be removed from required sources and not reported as unresolved-capability
+ return null;
+ }
+}
*/
package org.opendaylight.netconf.client.mdsal;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchemaProvider;
import org.opendaylight.netconf.client.mdsal.impl.DefaultBaseNetconfSchemaProvider;
-import org.opendaylight.yangtools.yang.parser.api.YangParserException;
import org.opendaylight.yangtools.yang.parser.impl.DefaultYangParserFactory;
public abstract class AbstractBaseSchemasTest {
- protected static BaseNetconfSchemaProvider BASE_SCHEMAS;
-
- @BeforeClass
- public static void initBaseSchemas() throws YangParserException {
- BASE_SCHEMAS = new DefaultBaseNetconfSchemaProvider(new DefaultYangParserFactory());
- }
-
- @AfterClass
- public static void freeBaseSchemas() {
- BASE_SCHEMAS = null;
- }
+ protected static final BaseNetconfSchemaProvider BASE_SCHEMAS =
+ new DefaultBaseNetconfSchemaProvider(new DefaultYangParserFactory());
}
*/
package org.opendaylight.netconf.client.mdsal;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
+import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
public abstract class AbstractTestModelTest extends AbstractBaseSchemasTest {
- protected static EffectiveModelContext SCHEMA_CONTEXT;
-
- @BeforeClass
- public static final void setupSchemaContext() {
- SCHEMA_CONTEXT = YangParserTestUtils.parseYangResource("/schemas/test-module.yang");
- }
-
- @AfterClass
- public static final void tearDownSchemaContext() {
- SCHEMA_CONTEXT = null;
- }
+ protected static final @NonNull EffectiveModelContext SCHEMA_CONTEXT =
+ YangParserTestUtils.parseYangResource("/schemas/test-module.yang");
}
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
-import com.google.common.util.concurrent.FluentFuture;
import java.net.InetSocketAddress;
import java.util.Optional;
import java.util.Set;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
-import org.opendaylight.mdsal.dom.api.DOMRpcResult;
-import org.opendaylight.mdsal.dom.api.DOMRpcService;
import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult;
+import org.opendaylight.netconf.client.mdsal.api.NetconfRpcService;
import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.GetSchema;
import org.opendaylight.yangtools.util.concurrent.FluentFutures;
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class MonitoringSchemaSourceProviderTest {
@Mock
- private DOMRpcService service;
+ private NetconfRpcService service;
private MonitoringSchemaSourceProvider provider;
@Before
public void setUp() throws Exception {
- final DOMRpcResult value = new DefaultDOMRpcResult(getNode(), Set.of());
- final FluentFuture<DOMRpcResult> response = FluentFutures.immediateFluentFuture(value);
- doReturn(response).when(service).invokeRpc(any(QName.class), any(ContainerNode.class));
+ doReturn(FluentFutures.immediateFluentFuture(new DefaultDOMRpcResult(getNode(), Set.of()))).when(service)
+ .invokeNetconf(any(), any());
provider = new MonitoringSchemaSourceProvider(
- new RemoteDeviceId("device1", InetSocketAddress.createUnresolved("localhost", 17830)), service);
+ new RemoteDeviceId("device1", InetSocketAddress.createUnresolved("localhost", 17830)), service);
}
@Test
final SourceIdentifier identifier = new SourceIdentifier("test", "2016-02-08");
final YangTextSource source = provider.getSource(identifier).get();
assertEquals(identifier, source.sourceId());
- verify(service).invokeRpc(GetSchema.QNAME,
+ verify(service).invokeNetconf(GetSchema.QNAME,
MonitoringSchemaSourceProvider.createGetSchemaRequest("test", Optional.of("2016-02-08")));
}
import static org.mockito.ArgumentMatchers.anyCollection;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.after;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import com.google.common.util.concurrent.SettableFuture;
import java.net.InetSocketAddress;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.opendaylight.netconf.api.messages.NetconfMessage;
import org.opendaylight.netconf.api.xml.XmlUtil;
import org.opendaylight.netconf.client.mdsal.NetconfDevice.EmptySchemaContextException;
+import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchema;
+import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchemaProvider;
import org.opendaylight.netconf.client.mdsal.api.NetconfDeviceSchemasResolver;
import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceHandler;
import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceServices;
+import org.opendaylight.netconf.client.mdsal.impl.DefaultDeviceNetconfSchemaProvider;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.NetconfState;
import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.available.capabilities.AvailableCapability.CapabilityOrigin;
import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.available.capabilities.AvailableCapabilityBuilder;
-import org.opendaylight.yangtools.concepts.Registration;
import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier;
import org.opendaylight.yangtools.yang.model.api.source.YangTextSource;
import org.opendaylight.yangtools.yang.model.repo.api.EffectiveModelContextFactory;
-import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException;
import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository;
import org.opendaylight.yangtools.yang.model.repo.api.SchemaResolutionException;
import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
@Mock
private SchemaSourceRegistry schemaRegistry;
+ @Mock
+ private DeviceNetconfSchemaProvider schemaProvider;
@BeforeClass
public static final void setupNotification() throws Exception {
NetconfDeviceTest.class.getResourceAsStream("/notification-payload.xml")));
}
- @Test
- public void testNetconfDeviceFlawedModelFailedResolution() throws Exception {
- final RemoteDeviceHandler facade = getFacade();
- final NetconfDeviceCommunicator listener = getListener();
-
- final EffectiveModelContextFactory schemaFactory = getSchemaFactory();
- final SchemaRepository schemaRepository = getSchemaRepository();
-
- final SchemaResolutionException schemaResolutionException =
- new SchemaResolutionException("fail first", TEST_SID, new Throwable("YangTools parser fail"));
- doAnswer(invocation -> {
- if (invocation.getArgument(0, Collection.class).size() == 2) {
- return Futures.immediateFailedFuture(schemaResolutionException);
- } else {
- return Futures.immediateFuture(SCHEMA_CONTEXT);
- }
- }).when(schemaFactory).createEffectiveModelContext(anyCollection());
-
- final NetconfDeviceSchemasResolver stateSchemasResolver =
- (deviceRpc, remoteSessionCapabilities, id, schemaContext) -> {
- final var first = SCHEMA_CONTEXT.getModules().iterator().next();
- final var qName = QName.create(first.getQNameModule(), first.getName());
- return Futures.immediateFuture(new NetconfStateSchemas(
- Set.of(qName, QName.create(first.getQNameModule(), "test-module2"))));
- };
-
- doReturn(mock(Registration.class)).when(schemaRegistry).registerSchemaSource(any(), any());
- final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice
- .SchemaResourcesDTO(schemaRegistry, schemaRepository, schemaFactory, stateSchemasResolver);
-
- final NetconfDevice device = new NetconfDeviceBuilder()
- .setReconnectOnSchemasChange(true)
- .setSchemaResourcesDTO(schemaResourcesDTO)
- .setGlobalProcessingExecutor(MoreExecutors.directExecutor())
- .setId(getId())
- .setSalFacade(facade)
- .setBaseSchemas(BASE_SCHEMAS)
- .build();
- // Monitoring supported
- final NetconfSessionPreferences sessionCaps = getSessionCaps(true, List.of(TEST_CAPABILITY, TEST_CAPABILITY2));
- device.onRemoteSessionUp(sessionCaps, listener);
-
- verify(facade, timeout(5000)).onDeviceConnected(any(NetconfDeviceSchema.class),
- any(NetconfSessionPreferences.class), any(RemoteDeviceServices.class));
- verify(schemaFactory, times(2)).createEffectiveModelContext(anyCollection());
- }
-
@Test
public void testNetconfDeviceFailFirstSchemaFailSecondEmpty() throws Exception {
- final RemoteDeviceHandler facade = getFacade();
- final NetconfDeviceCommunicator listener = getListener();
+ final var facade = getFacade();
+ final var listener = getListener();
- final EffectiveModelContextFactory schemaFactory = getSchemaFactory();
- final SchemaRepository schemaRepository = getSchemaRepository();
+ final var schemaFactory = getSchemaFactory();
// Make fallback attempt to fail due to empty resolved sources
- final SchemaResolutionException schemaResolutionException = new SchemaResolutionException("fail first",
+ final var schemaResolutionException = new SchemaResolutionException("fail first",
new SourceIdentifier("test-module", "2013-07-22"), new Throwable());
doReturn(Futures.immediateFailedFuture(schemaResolutionException))
.when(schemaFactory).createEffectiveModelContext(anyCollection());
- final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice
- .SchemaResourcesDTO(schemaRegistry, schemaRepository, schemaFactory, STATE_SCHEMAS_RESOLVER);
- final NetconfDevice device = new NetconfDeviceBuilder()
- .setReconnectOnSchemasChange(true)
- .setSchemaResourcesDTO(schemaResourcesDTO)
- .setGlobalProcessingExecutor(MoreExecutors.directExecutor())
- .setId(getId())
- .setSalFacade(facade)
- .setBaseSchemas(BASE_SCHEMAS)
- .build();
+ final var device = new NetconfDeviceBuilder()
+ .setReconnectOnSchemasChange(true)
+ .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider(getSchemaRepository(), schemaFactory,
+ STATE_SCHEMAS_RESOLVER))
+ .setProcessingExecutor(MoreExecutors.directExecutor())
+ .setId(getId())
+ .setSalFacade(facade)
+ .setBaseSchemaProvider(BASE_SCHEMAS)
+ .build();
// Monitoring not supported
- final NetconfSessionPreferences sessionCaps = getSessionCaps(false, List.of(TEST_CAPABILITY));
- device.onRemoteSessionUp(sessionCaps, listener);
+ device.onRemoteSessionUp(getSessionCaps(false, TEST_CAPABILITY), listener);
final var captor = ArgumentCaptor.forClass(Throwable.class);
verify(facade, timeout(5000)).onDeviceFailed(captor.capture());
verify(schemaFactory, times(1)).createEffectiveModelContext(anyCollection());
}
- @Test
- public void testNetconfDeviceMissingSource() throws Exception {
- final RemoteDeviceHandler facade = getFacade();
- final NetconfDeviceCommunicator listener = getListener();
-
- final EffectiveModelContextFactory schemaFactory = getSchemaFactory();
- final SchemaRepository schemaRepository = getSchemaRepository();
-
- // Make fallback attempt to fail due to empty resolved sources
- final MissingSchemaSourceException schemaResolutionException =
- new MissingSchemaSourceException(TEST_SID, "fail first");
- doReturn(Futures.immediateFailedFuture(schemaResolutionException))
- .when(schemaRepository).getSchemaSource(eq(TEST_SID), eq(YangTextSource.class));
- doAnswer(invocation -> {
- if (invocation.getArgument(0, Collection.class).size() == 2) {
- return Futures.immediateFailedFuture(schemaResolutionException);
- } else {
- return Futures.immediateFuture(SCHEMA_CONTEXT);
- }
- }).when(schemaFactory).createEffectiveModelContext(anyCollection());
-
- final NetconfDeviceSchemasResolver stateSchemasResolver =
- (deviceRpc, remoteSessionCapabilities, id, schemaContext) -> {
- final var first = SCHEMA_CONTEXT.getModules().iterator().next();
- final var qName = QName.create(first.getQNameModule(), first.getName());
- return Futures.immediateFuture(new NetconfStateSchemas(
- Set.of(qName, QName.create(first.getQNameModule(), "test-module2"))));
- };
-
- doReturn(mock(Registration.class)).when(schemaRegistry).registerSchemaSource(any(), any());
- final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice
- .SchemaResourcesDTO(schemaRegistry, schemaRepository, schemaFactory, stateSchemasResolver);
-
- final NetconfDevice device = new NetconfDeviceBuilder()
- .setReconnectOnSchemasChange(true)
- .setSchemaResourcesDTO(schemaResourcesDTO)
- .setGlobalProcessingExecutor(MoreExecutors.directExecutor())
- .setBaseSchemas(BASE_SCHEMAS)
- .setId(getId())
- .setSalFacade(facade)
- .build();
- // Monitoring supported
- final NetconfSessionPreferences sessionCaps =
- getSessionCaps(true, List.of(TEST_CAPABILITY, TEST_CAPABILITY2));
- device.onRemoteSessionUp(sessionCaps, listener);
-
- verify(facade, timeout(5000)).onDeviceConnected(any(NetconfDeviceSchema.class),
- any(NetconfSessionPreferences.class), any(RemoteDeviceServices.class));
- verify(schemaFactory, times(1)).createEffectiveModelContext(anyCollection());
- }
-
private static SchemaRepository getSchemaRepository() {
- final SchemaRepository mock = mock(SchemaRepository.class);
- final YangTextSource mockRep = mock(YangTextSource.class);
+ final var mock = mock(SchemaRepository.class);
+ final var mockRep = mock(YangTextSource.class);
doReturn(Futures.immediateFuture(mockRep))
.when(mock).getSchemaSource(any(SourceIdentifier.class), eq(YangTextSource.class));
return mock;
@Test
public void testNotificationBeforeSchema() throws Exception {
- final RemoteDeviceHandler facade = getFacade();
- final NetconfDeviceCommunicator listener = getListener();
- final EffectiveModelContextFactory schemaContextProviderFactory = mock(EffectiveModelContextFactory.class);
- final SettableFuture<SchemaContext> schemaFuture = SettableFuture.create();
- doReturn(schemaFuture).when(schemaContextProviderFactory).createEffectiveModelContext(anyCollection());
- final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(schemaRegistry,
- getSchemaRepository(), schemaContextProviderFactory, STATE_SCHEMAS_RESOLVER);
- final NetconfDevice device = new NetconfDeviceBuilder()
- .setReconnectOnSchemasChange(true)
- .setSchemaResourcesDTO(schemaResourcesDTO)
- .setGlobalProcessingExecutor(MoreExecutors.directExecutor())
- .setId(getId())
- .setSalFacade(facade)
- .setBaseSchemas(BASE_SCHEMAS)
- .build();
-
- final NetconfSessionPreferences sessionCaps = getSessionCaps(true, List.of(TEST_CAPABILITY));
- device.onRemoteSessionUp(sessionCaps, listener);
+ final var facade = getFacade();
+ final var deviceSchemaProvider = mock(DeviceNetconfSchemaProvider.class);
+ final var schemaFuture = SettableFuture.<DeviceNetconfSchema>create();
+ doReturn(schemaFuture).when(deviceSchemaProvider).deviceNetconfSchemaFor(any(), any(), any(), any(), any());
+
+ final var device = new NetconfDeviceBuilder()
+ .setReconnectOnSchemasChange(true)
+ .setDeviceSchemaProvider(deviceSchemaProvider)
+ .setProcessingExecutor(MoreExecutors.directExecutor())
+ .setId(getId())
+ .setSalFacade(facade)
+ .setBaseSchemaProvider(BASE_SCHEMAS)
+ .build();
+
+ final var sessionCaps = getSessionCaps(true, TEST_CAPABILITY);
+ device.onRemoteSessionUp(sessionCaps, getListener());
device.onNotification(NOTIFICATION);
device.onNotification(NOTIFICATION);
verify(facade, times(0)).onNotification(any(DOMNotification.class));
- verify(facade, times(0)).onNotification(any(DOMNotification.class));
- schemaFuture.set(NetconfToNotificationTest.getNotificationSchemaContext(getClass(), false));
+ // Now enable schema
+ schemaFuture.set(new DeviceNetconfSchema(NetconfDeviceCapabilities.empty(),
+ NetconfToNotificationTest.getNotificationSchemaContext(NetconfDeviceTest.class, false)));
+
verify(facade, timeout(10000).times(2)).onNotification(any(DOMNotification.class));
device.onNotification(NOTIFICATION);
- verify(facade, timeout(10000).times(3)).onNotification(any(DOMNotification.class));
+ verify(facade, times(3)).onNotification(any(DOMNotification.class));
}
@Test
public void testNetconfDeviceReconnect() throws Exception {
- final RemoteDeviceHandler facade = getFacade();
- final NetconfDeviceCommunicator listener = getListener();
+ final var facade = getFacade();
+ final var listener = getListener();
+
+ final var deviceSchemaProvider = mockDeviceNetconfSchemaProvider();
- final EffectiveModelContextFactory schemaContextProviderFactory = getSchemaFactory();
-
- final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(
- schemaRegistry, getSchemaRepository(), schemaContextProviderFactory, STATE_SCHEMAS_RESOLVER);
- final NetconfDevice device = new NetconfDeviceBuilder()
- .setReconnectOnSchemasChange(true)
- .setSchemaResourcesDTO(schemaResourcesDTO)
- .setGlobalProcessingExecutor(MoreExecutors.directExecutor())
- .setId(getId())
- .setSalFacade(facade)
- .setBaseSchemas(BASE_SCHEMAS)
- .build();
- final NetconfSessionPreferences sessionCaps = getSessionCaps(true,
- List.of(TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION));
+ final var device = new NetconfDeviceBuilder()
+ .setReconnectOnSchemasChange(true)
+ .setDeviceSchemaProvider(deviceSchemaProvider)
+ .setProcessingExecutor(MoreExecutors.directExecutor())
+ .setId(getId())
+ .setSalFacade(facade)
+ .setBaseSchemaProvider(BASE_SCHEMAS)
+ .build();
+ final var sessionCaps = getSessionCaps(true,
+ TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION);
device.onRemoteSessionUp(sessionCaps, listener);
- verify(schemaContextProviderFactory, timeout(5000)).createEffectiveModelContext(anyCollection());
verify(facade, timeout(5000)).onDeviceConnected(
any(NetconfDeviceSchema.class), any(NetconfSessionPreferences.class), any(RemoteDeviceServices.class));
device.onRemoteSessionUp(sessionCaps, listener);
- verify(schemaContextProviderFactory, timeout(5000).times(2)).createEffectiveModelContext(anyCollection());
verify(facade, timeout(5000).times(2)).onDeviceConnected(
any(NetconfDeviceSchema.class), any(NetconfSessionPreferences.class), any(RemoteDeviceServices.class));
}
@Test
public void testNetconfDeviceDisconnectListenerCallCancellation() throws Exception {
- final RemoteDeviceHandler facade = getFacade();
- final NetconfDeviceCommunicator listener = getListener();
- final EffectiveModelContextFactory schemaContextProviderFactory = mock(EffectiveModelContextFactory.class);
- final SettableFuture<SchemaContext> schemaFuture = SettableFuture.create();
- doReturn(schemaFuture).when(schemaContextProviderFactory).createEffectiveModelContext(anyCollection());
- final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(schemaRegistry,
- getSchemaRepository(), schemaContextProviderFactory, STATE_SCHEMAS_RESOLVER);
- final NetconfDevice device = new NetconfDeviceBuilder()
- .setReconnectOnSchemasChange(true)
- .setSchemaResourcesDTO(schemaResourcesDTO)
- .setGlobalProcessingExecutor(MoreExecutors.directExecutor())
- .setId(getId())
- .setSalFacade(facade)
- .setBaseSchemas(BASE_SCHEMAS)
- .build();
- final NetconfSessionPreferences sessionCaps = getSessionCaps(true,
- List.of(TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION));
+ final var facade = getFacade();
+ final var schemaFuture = SettableFuture.<DeviceNetconfSchema>create();
+ doReturn(schemaFuture).when(schemaProvider).deviceNetconfSchemaFor(any(), any(), any(), any(), any());
+
+ final var device = new NetconfDeviceBuilder()
+ .setReconnectOnSchemasChange(true)
+ .setDeviceSchemaProvider(schemaProvider)
+ .setProcessingExecutor(MoreExecutors.directExecutor())
+ .setId(getId())
+ .setSalFacade(facade)
+ .setBaseSchemaProvider(BASE_SCHEMAS)
+ .build();
//session up, start schema resolution
- device.onRemoteSessionUp(sessionCaps, listener);
+ device.onRemoteSessionUp(getSessionCaps(true,
+ TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION), getListener());
//session closed
device.onRemoteSessionDown();
verify(facade, timeout(5000)).onDeviceDisconnected();
//complete schema setup
- schemaFuture.set(SCHEMA_CONTEXT);
+ schemaFuture.set(new DeviceNetconfSchema(NetconfDeviceCapabilities.empty(), SCHEMA_CONTEXT));
//facade.onDeviceDisconnected() was called, so facade.onDeviceConnected() shouldn't be called anymore
verify(facade, after(1000).never()).onDeviceConnected(any(), any(), any(RemoteDeviceServices.class));
}
@Test
public void testNetconfDeviceReconnectBeforeSchemaSetup() throws Exception {
- final RemoteDeviceHandler facade = getFacade();
+ final var facade = getFacade();
- final EffectiveModelContextFactory schemaContextProviderFactory = mock(EffectiveModelContextFactory.class);
- final SettableFuture<SchemaContext> schemaFuture = SettableFuture.create();
+ final var schemaContextProviderFactory = mock(EffectiveModelContextFactory.class);
+ final var schemaFuture = SettableFuture.<EffectiveModelContext>create();
doReturn(schemaFuture).when(schemaContextProviderFactory).createEffectiveModelContext(anyCollection());
- final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(
- schemaRegistry, getSchemaRepository(), schemaContextProviderFactory, STATE_SCHEMAS_RESOLVER);
- final NetconfDevice device = new NetconfDeviceBuilder()
+ final var device = new NetconfDeviceBuilder()
.setReconnectOnSchemasChange(true)
- .setSchemaResourcesDTO(schemaResourcesDTO)
- .setGlobalProcessingExecutor(MoreExecutors.directExecutor())
+ .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider(getSchemaRepository(),
+ schemaContextProviderFactory, STATE_SCHEMAS_RESOLVER))
+ .setProcessingExecutor(MoreExecutors.directExecutor())
.setId(getId())
.setSalFacade(facade)
- .setBaseSchemas(BASE_SCHEMAS)
+ .setBaseSchemaProvider(BASE_SCHEMAS)
.build();
- final NetconfSessionPreferences sessionCaps = getSessionCaps(true,
- List.of(TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION));
+ final var sessionCaps = getSessionCaps(true,
+ TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION);
final NetconfDeviceCommunicator listener = getListener();
// session up, start schema resolution
@Test
public void testNetconfDeviceAvailableCapabilitiesBuilding() throws Exception {
- final RemoteDeviceHandler facade = getFacade();
- final NetconfDeviceCommunicator listener = getListener();
+ final var facade = getFacade();
+ final var listener = getListener();
+
+ final var netconfSpy = spy(new NetconfDeviceBuilder()
+ .setReconnectOnSchemasChange(true)
+ .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider())
+ .setProcessingExecutor(MoreExecutors.directExecutor())
+ .setId(getId())
+ .setSalFacade(facade)
+ .setBaseSchemaProvider(BASE_SCHEMAS)
+ .build());
- final EffectiveModelContextFactory schemaContextProviderFactory = getSchemaFactory();
-
- final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(schemaRegistry,
- getSchemaRepository(), schemaContextProviderFactory, STATE_SCHEMAS_RESOLVER);
- final NetconfDevice device = new NetconfDeviceBuilder()
- .setReconnectOnSchemasChange(true)
- .setSchemaResourcesDTO(schemaResourcesDTO)
- .setGlobalProcessingExecutor(MoreExecutors.directExecutor())
- .setId(getId())
- .setSalFacade(facade)
- .setBaseSchemas(BASE_SCHEMAS)
- .build();
- final NetconfDevice netconfSpy = spy(device);
-
- final NetconfSessionPreferences sessionCaps = getSessionCaps(true,
- List.of(TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION));
- final Map<QName, CapabilityOrigin> moduleBasedCaps = new HashMap<>();
+ final var sessionCaps = getSessionCaps(true,
+ TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION);
+ final var moduleBasedCaps = new HashMap<QName, CapabilityOrigin>();
moduleBasedCaps.putAll(sessionCaps.moduleBasedCaps());
- moduleBasedCaps
- .put(QName.create("(test:qname:side:loading)test"), CapabilityOrigin.UserDefined);
+ moduleBasedCaps.put(QName.create("(test:qname:side:loading)test"), CapabilityOrigin.UserDefined);
netconfSpy.onRemoteSessionUp(sessionCaps.replaceModuleCaps(moduleBasedCaps), listener);
@Test
public void testNetconfDeviceNotificationsModelNotPresentWithCapability() throws Exception {
- final RemoteDeviceHandler facade = getFacade();
- final NetconfDeviceCommunicator listener = getListener();
- final EffectiveModelContextFactory schemaContextProviderFactory = getSchemaFactory();
-
- final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(schemaRegistry,
- getSchemaRepository(), schemaContextProviderFactory, STATE_SCHEMAS_RESOLVER);
- final NetconfDevice device = new NetconfDeviceBuilder()
- .setSchemaResourcesDTO(schemaResourcesDTO)
- .setGlobalProcessingExecutor(MoreExecutors.directExecutor())
- .setId(getId())
- .setSalFacade(facade)
- .setBaseSchemas(BASE_SCHEMAS)
- .build();
- final NetconfDevice netconfSpy = spy(device);
+ final var facade = getFacade();
+ final var netconfSpy = spy(new NetconfDeviceBuilder()
+ .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider())
+ .setProcessingExecutor(MoreExecutors.directExecutor())
+ .setId(getId())
+ .setSalFacade(facade)
+ .setBaseSchemaProvider(BASE_SCHEMAS)
+ .build());
- netconfSpy.onRemoteSessionUp(getSessionCaps(false, List.of(CapabilityURN.NOTIFICATION)), listener);
+ netconfSpy.onRemoteSessionUp(getSessionCaps(false, CapabilityURN.NOTIFICATION), getListener());
final var argument = ArgumentCaptor.forClass(NetconfDeviceSchema.class);
verify(facade, timeout(5000)).onDeviceConnected(argument.capture(), any(NetconfSessionPreferences.class),
}
@Test
- public void testNetconfDeviceNotificationsCapabilityIsNotPresent() throws Exception {
- final RemoteDeviceHandler facade = getFacade();
- final NetconfDeviceCommunicator listener = getListener();
- final EffectiveModelContextFactory schemaContextProviderFactory = getSchemaFactory();
-
- final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(schemaRegistry,
- getSchemaRepository(), schemaContextProviderFactory, STATE_SCHEMAS_RESOLVER);
- final NetconfDevice device = new NetconfDeviceBuilder()
- .setSchemaResourcesDTO(schemaResourcesDTO)
- .setGlobalProcessingExecutor(MoreExecutors.directExecutor())
- .setId(getId())
- .setSalFacade(facade)
- .setBaseSchemas(BASE_SCHEMAS)
- .build();
- final NetconfDevice netconfSpy = spy(device);
-
- final NetconfSessionPreferences sessionCaps = getSessionCaps(false,
- List.of(TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION));
-
- netconfSpy.onRemoteSessionUp(sessionCaps, listener);
-
- final var argument = ArgumentCaptor.forClass(NetconfDeviceSchema.class);
- verify(facade, timeout(5000)).onDeviceConnected(argument.capture(), any(NetconfSessionPreferences.class),
- any(RemoteDeviceServices.class));
+ public void testNetconfDeviceNotificationsModelIsPresent() throws Exception {
+ final var facade = getFacade();
+ final var listener = getListener();
- assertEquals(Set.of(new AvailableCapabilityBuilder()
- .setCapability("(test:namespace?revision=2013-07-22)test-module")
- .setCapabilityOrigin(CapabilityOrigin.DeviceAdvertised)
- .build()), argument.getValue().capabilities().resolvedCapabilities());
- }
+ final var netconfSpy = spy(new NetconfDeviceBuilder()
+ .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider())
+ .setProcessingExecutor(MoreExecutors.directExecutor())
+ .setId(getId())
+ .setSalFacade(facade)
+ .setBaseSchemaProvider(BASE_SCHEMAS)
+ .build());
- @Test
- public void testNetconfDeviceNotificationsModelIsPresent() throws Exception {
- final RemoteDeviceHandler facade = getFacade();
- final NetconfDeviceCommunicator listener = getListener();
- final EffectiveModelContextFactory schemaContextProviderFactory = getSchemaFactory();
-
- final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(schemaRegistry,
- getSchemaRepository(), schemaContextProviderFactory, STATE_SCHEMAS_RESOLVER);
- final NetconfDevice device = new NetconfDeviceBuilder()
- .setSchemaResourcesDTO(schemaResourcesDTO)
- .setGlobalProcessingExecutor(MoreExecutors.directExecutor())
- .setId(getId())
- .setSalFacade(facade)
- .setBaseSchemas(BASE_SCHEMAS)
- .build();
- final NetconfDevice netconfSpy = spy(device);
-
- netconfSpy.onRemoteSessionUp(getSessionCaps(false, List.of()).replaceModuleCaps(Map.of(
+ netconfSpy.onRemoteSessionUp(getSessionCaps(false).replaceModuleCaps(Map.of(
org.opendaylight.yang.svc.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714
.YangModuleInfoImpl.getInstance().getName(), CapabilityOrigin.DeviceAdvertised,
org.opendaylight.yang.svc.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715
.build()), argument.getValue().capabilities().resolvedCapabilities());
}
- private static EffectiveModelContextFactory getSchemaFactory() throws Exception {
- final EffectiveModelContextFactory schemaFactory = mock(EffectiveModelContextFactory.class);
+ private static EffectiveModelContextFactory getSchemaFactory() {
+ final var schemaFactory = mock(EffectiveModelContextFactory.class);
doReturn(Futures.immediateFuture(SCHEMA_CONTEXT))
.when(schemaFactory).createEffectiveModelContext(anyCollection());
return schemaFactory;
return mock;
}
+ private DeviceNetconfSchemaProvider mockDeviceNetconfSchemaProvider() {
+ return mockDeviceNetconfSchemaProvider(getSchemaRepository(), getSchemaFactory(), STATE_SCHEMAS_RESOLVER);
+ }
+
+ private DeviceNetconfSchemaProvider mockDeviceNetconfSchemaProvider(final SchemaRepository schemaRepository,
+ final EffectiveModelContextFactory schemaFactory, final NetconfDeviceSchemasResolver stateSchemasResolver) {
+ return new DefaultDeviceNetconfSchemaProvider(schemaRegistry, schemaRepository, schemaFactory,
+ stateSchemasResolver);
+ }
+
public RemoteDeviceId getId() {
return new RemoteDeviceId("test-D", InetSocketAddress.createUnresolved("localhost", 22));
}
public NetconfSessionPreferences getSessionCaps(final boolean addMonitor,
- final Collection<String> additionalCapabilities) {
+ final String... additionalCapabilities) {
final var capabilities = new ArrayList<String>();
capabilities.add(CapabilityURN.BASE);
capabilities.add(CapabilityURN.BASE_1_1);
if (addMonitor) {
capabilities.add(NetconfState.QNAME.getNamespace().toString());
}
- capabilities.addAll(additionalCapabilities);
+ capabilities.addAll(List.of(additionalCapabilities));
return NetconfSessionPreferences.fromStrings(capabilities);
}
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.opendaylight.mdsal.dom.api.DOMRpcImplementationNotAvailableException;
-import org.opendaylight.mdsal.dom.api.DOMRpcService;
import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult;
+import org.opendaylight.netconf.client.mdsal.api.NetconfRpcService;
import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
import org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil;
private static ContainerNode SCHEMAS_PAYLOAD;
@Mock
- private DOMRpcService rpc;
+ private NetconfRpcService rpc;
@BeforeClass
public static void setUp() throws Exception {
.build())
.build())
.build();
- doReturn(Futures.immediateFuture(new DefaultDOMRpcResult(rpcReply))).when(rpc).invokeRpc(eq(Get.QNAME), any());
+ doReturn(Futures.immediateFuture(new DefaultDOMRpcResult(rpcReply))).when(rpc)
+ .invokeNetconf(eq(Get.QNAME), any());
final var stateSchemas = assertSchemas(CAPS);
final var availableYangSchemasQNames = stateSchemas.getAvailableYangSchemasQNames();
assertEquals(69, availableYangSchemasQNames.size());
@Test
public void testCreateFail() throws Exception {
final var domEx = new DOMRpcImplementationNotAvailableException("not available");
- doReturn(Futures.immediateFailedFuture(domEx)).when(rpc).invokeRpc(eq(Get.QNAME), any());
+ doReturn(Futures.immediateFailedFuture(domEx)).when(rpc).invokeNetconf(eq(Get.QNAME), any());
assertSame(domEx, assertSchemasFailure());
}
@Test
public void testCreateRpcError() throws Exception {
final var rpcError = RpcResultBuilder.newError(ErrorType.RPC, new ErrorTag("fail"), "fail");
- doReturn(Futures.immediateFuture(new DefaultDOMRpcResult(rpcError))).when(rpc).invokeRpc(eq(Get.QNAME), any());
+ doReturn(Futures.immediateFuture(new DefaultDOMRpcResult(rpcError))).when(rpc)
+ .invokeNetconf(eq(Get.QNAME), any());
final var ex = assertInstanceOf(OperationFailedException.class, assertSchemasFailure());
assertEquals(List.of(rpcError), ex.getErrorList());
--- /dev/null
+/*
+ * Copyright (c) 2024 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.client.mdsal.impl;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyCollection;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import com.google.common.util.concurrent.Futures;
+import java.net.InetSocketAddress;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.opendaylight.netconf.client.mdsal.AbstractTestModelTest;
+import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
+import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.available.capabilities.AvailableCapability.CapabilityOrigin;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.available.capabilities.AvailableCapabilityBuilder;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.source.YangTextSource;
+import org.opendaylight.yangtools.yang.model.repo.api.EffectiveModelContextFactory;
+import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException;
+import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository;
+import org.opendaylight.yangtools.yang.model.repo.api.SchemaResolutionException;
+import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceProvider;
+
+@ExtendWith(MockitoExtension.class)
+class SchemaSetupTest extends AbstractTestModelTest {
+ private static final RemoteDeviceId DEVICE_ID = new RemoteDeviceId("someDevice", new InetSocketAddress(42));
+ private static final String TEST_NAMESPACE = "test:namespace";
+ private static final String TEST_MODULE = "test-module";
+ private static final String TEST_REVISION = "2013-07-22";
+ private static final SourceIdentifier TEST_SID = new SourceIdentifier(TEST_MODULE, TEST_REVISION);
+ private static final SourceIdentifier TEST_SID2 = new SourceIdentifier(TEST_MODULE + "2", TEST_REVISION);
+ private static final QName TEST_QNAME = QName.create(TEST_NAMESPACE, TEST_REVISION, TEST_MODULE);
+ private static final QName TEST_QNAME2 = QName.create(TEST_NAMESPACE, TEST_REVISION, TEST_MODULE + "2");
+
+ @Mock
+ private EffectiveModelContextFactory contextFactory;
+ @Mock
+ private SchemaRepository schemaRepository;
+ @Mock
+ private SchemaSourceProvider<YangTextSource> sourceProvider;
+ @Mock
+ private YangTextSource source;
+
+ @Test
+ void testNetconfDeviceFlawedModelFailedResolution() throws Exception {
+ final var ex = new SchemaResolutionException("fail first", TEST_SID, new Throwable("YangTools parser fail"));
+ doAnswer(invocation -> invocation.getArgument(0, Collection.class).size() == 2
+ ? Futures.immediateFailedFuture(ex) : Futures.immediateFuture(SCHEMA_CONTEXT))
+ .when(contextFactory).createEffectiveModelContext(anyCollection());
+
+ doReturn(Futures.immediateFuture(source)).when(schemaRepository)
+ .getSchemaSource(any(), eq(YangTextSource.class));
+
+ final var setup = new SchemaSetup(schemaRepository, contextFactory, DEVICE_ID,
+ new DeviceSources(Set.of(TEST_QNAME, TEST_QNAME2), Set.of(TEST_QNAME, TEST_QNAME2), sourceProvider),
+ NetconfSessionPreferences.fromStrings(Set.of()));
+
+ final var result = Futures.getDone(setup.startResolution());
+ verify(contextFactory, times(2)).createEffectiveModelContext(anyCollection());
+ assertSame(SCHEMA_CONTEXT, result.modelContext());
+ }
+
+ @Test
+ void testNetconfDeviceMissingSource() throws Exception {
+ // Make fallback attempt to fail due to empty resolved sources
+ final var ex = new MissingSchemaSourceException(TEST_SID, "fail first");
+ doReturn(Futures.immediateFailedFuture(ex))
+ .when(schemaRepository).getSchemaSource(TEST_SID, YangTextSource.class);
+ doReturn(Futures.immediateFuture(source)).when(schemaRepository)
+ .getSchemaSource(TEST_SID2, YangTextSource.class);
+ doReturn(Futures.immediateFuture(SCHEMA_CONTEXT)).when(contextFactory)
+ .createEffectiveModelContext(anyCollection());
+
+ final var setup = new SchemaSetup(schemaRepository, contextFactory, DEVICE_ID,
+ new DeviceSources(Set.of(TEST_QNAME, TEST_QNAME2), Set.of(TEST_QNAME, TEST_QNAME2), sourceProvider),
+ NetconfSessionPreferences.fromStrings(Set.of()));
+
+ final var result = Futures.getDone(setup.startResolution());
+ final var captor = ArgumentCaptor.forClass(Collection.class);
+ verify(contextFactory).createEffectiveModelContext(captor.capture());
+ assertEquals(List.of(TEST_SID2), captor.getValue());
+ assertSame(SCHEMA_CONTEXT, result.modelContext());
+ }
+
+ @Test
+ void testNetconfDeviceNotificationsCapabilityIsNotPresent() throws Exception {
+ doReturn(Futures.immediateFuture(source)).when(schemaRepository)
+ .getSchemaSource(any(), eq(YangTextSource.class));
+ doReturn(Futures.immediateFuture(SCHEMA_CONTEXT)).when(contextFactory)
+ .createEffectiveModelContext(anyCollection());
+
+ final var setup = new SchemaSetup(schemaRepository, contextFactory, DEVICE_ID,
+ new DeviceSources(Set.of(TEST_QNAME), Set.of(TEST_QNAME), sourceProvider),
+ NetconfSessionPreferences.fromStrings(
+ Set.of(TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION)));
+
+ final var result = Futures.getDone(setup.startResolution());
+ assertEquals(Set.of(new AvailableCapabilityBuilder()
+ .setCapability("(test:namespace?revision=2013-07-22)test-module")
+ .setCapabilityOrigin(CapabilityOrigin.DeviceAdvertised)
+ .build()), result.capabilities().resolvedCapabilities());
+ }
+}