* 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.restconf.nb.rfc8040.handlers;
+package org.opendaylight.restconf.nb.rfc8040.legacy;
import static java.util.Objects.requireNonNull;
-import static org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.monitoring.rev170126.$YangModuleInfoImpl.qnameOf;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Throwables;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.inject.Singleton;
-import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
import org.opendaylight.mdsal.dom.api.DOMDataBroker;
import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
import org.opendaylight.mdsal.dom.api.DOMSchemaService;
-import org.opendaylight.restconf.api.query.AbstractReplayParam;
-import org.opendaylight.restconf.api.query.ChangedLeafNodesOnlyParam;
-import org.opendaylight.restconf.api.query.DepthParam;
-import org.opendaylight.restconf.api.query.FieldsParam;
-import org.opendaylight.restconf.api.query.FilterParam;
-import org.opendaylight.restconf.api.query.LeafNodesOnlyParam;
-import org.opendaylight.restconf.api.query.PrettyPrintParam;
-import org.opendaylight.restconf.api.query.SkipNotificationDataParam;
-import org.opendaylight.restconf.api.query.WithDefaultsParam;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.nb.rfc8040.Rfc8040.IetfYangLibrary;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.monitoring.rev170126.RestconfState;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.monitoring.rev170126.restconf.state.Capabilities;
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.module.list.Module.ConformanceType;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.module.list.module.Deviation;
import org.slf4j.LoggerFactory;
/**
- * Implementation of {@link SchemaContextHandler}.
+ * A component which maintains the state of {@code ietf-yang-library} inside the datastore.
*/
-// FIXME: this really is a service which is maintaining ietf-yang-library contents inside the datastore. It really
-// should live in MD-SAL and be a dynamic store fragment. As a first step we should be turning this into a
-// completely standalone application.
+// FIXME: this should be reconciled with the two other implementations we have.
@Singleton
@Component(service = { })
public final class SchemaContextHandler implements EffectiveModelContextListener, AutoCloseable {
private static final Logger LOG = LoggerFactory.getLogger(SchemaContextHandler.class);
- @VisibleForTesting
- static final @NonNull QName CAPABILITY_QNAME = qnameOf("capability");
-
private static final NodeIdentifier MODULE_CONFORMANCE_NODEID =
NodeIdentifier.create(QName.create(IetfYangLibrary.MODULE_QNAME, "conformance-type").intern());
private static final NodeIdentifier MODULE_FEATURE_NODEID =
putData(mapModulesByIetfYangLibraryYang(context.getModules(), context,
String.valueOf(moduleSetId.incrementAndGet())));
}
-
- if (schemaContext.findModuleStatement(RestconfState.QNAME.getModule()).isPresent()) {
- putData(mapCapabilites());
- }
}
@VisibleForTesting
}
}
- /**
- * Map capabilites by ietf-restconf-monitoring.
- *
- * @return mapped capabilites
- */
- @VisibleForTesting
- static ContainerNode mapCapabilites() {
- return Builders.containerBuilder()
- .withNodeIdentifier(new NodeIdentifier(RestconfState.QNAME))
- .withChild(Builders.containerBuilder()
- .withNodeIdentifier(new NodeIdentifier(Capabilities.QNAME))
- .withChild(Builders.<String>orderedLeafSetBuilder()
- .withNodeIdentifier(new NodeIdentifier(CAPABILITY_QNAME))
- .withChildValue(DepthParam.capabilityUri().toString())
- .withChildValue(FieldsParam.capabilityUri().toString())
- .withChildValue(FilterParam.capabilityUri().toString())
- .withChildValue(AbstractReplayParam.capabilityUri().toString())
- .withChildValue(WithDefaultsParam.capabilityUri().toString())
- .withChildValue(PrettyPrintParam.capabilityUri().toString())
- .withChildValue(LeafNodesOnlyParam.capabilityUri().toString())
- .withChildValue(SkipNotificationDataParam.capabilityUri().toString())
- .withChildValue(ChangedLeafNodesOnlyParam.capabilityUri().toString())
- .build())
- .build())
- .build();
- }
-
/**
* Map data from modules to {@link NormalizedNode}.
*
--- /dev/null
+/*
+ * Copyright (c) 2022 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.restconf.nb.rfc8040.monitoring;
+
+import static java.util.Objects.requireNonNull;
+import static org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.monitoring.rev170126.$YangModuleInfoImpl.qnameOf;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.MoreExecutors;
+import javax.annotation.PreDestroy;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import org.checkerframework.checker.lock.qual.Holding;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.common.api.CommitInfo;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.dom.api.DOMDataBroker;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeTransaction;
+import org.opendaylight.mdsal.dom.api.DOMSchemaService;
+import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
+import org.opendaylight.mdsal.dom.api.DOMTransactionChainListener;
+import org.opendaylight.restconf.api.query.AbstractReplayParam;
+import org.opendaylight.restconf.api.query.DepthParam;
+import org.opendaylight.restconf.api.query.FieldsParam;
+import org.opendaylight.restconf.api.query.FilterParam;
+import org.opendaylight.restconf.api.query.LeafNodesOnlyParam;
+import org.opendaylight.restconf.api.query.PrettyPrintParam;
+import org.opendaylight.restconf.api.query.SkipNotificationDataParam;
+import org.opendaylight.restconf.api.query.WithDefaultsParam;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.monitoring.rev170126.RestconfState;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.monitoring.rev170126.restconf.state.Capabilities;
+import org.opendaylight.yangtools.concepts.Registration;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContextListener;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A simple component which maintains {@link Capabilities} in the operational datastore.
+ */
+@Singleton
+@Component(service = { })
+public final class CapabilitiesWriter
+ implements AutoCloseable, EffectiveModelContextListener, DOMTransactionChainListener {
+ private static final Logger LOG = LoggerFactory.getLogger(CapabilitiesWriter.class);
+
+ @VisibleForTesting
+ static final @NonNull NodeIdentifier CAPABILITY = NodeIdentifier.create(qnameOf("capability"));
+
+ private static final YangInstanceIdentifier PATH = YangInstanceIdentifier.of(
+ NodeIdentifier.create(RestconfState.QNAME), NodeIdentifier.create(Capabilities.QNAME), CAPABILITY);
+
+ private final DOMDataBroker dataBroker;
+
+ private DOMTransactionChain txChain;
+ private Registration reg;
+
+ private boolean written;
+
+ @Inject
+ @Activate
+ public CapabilitiesWriter(@Reference final DOMDataBroker dataBroker,
+ @Reference final DOMSchemaService schemaService) {
+ this.dataBroker = requireNonNull(dataBroker);
+ reg = schemaService.registerSchemaContextListener(this);
+ }
+
+ @PreDestroy
+ @Deactivate
+ @Override
+ public synchronized void close() {
+ if (reg == null) {
+ return;
+ }
+ reg.close();
+ reg = null;
+ deleteRestconfState();
+ if (txChain != null) {
+ txChain.close();
+ }
+ }
+
+ @Override
+ public synchronized void onTransactionChainFailed(final DOMTransactionChain chain,
+ final DOMDataTreeTransaction transaction, final Throwable cause) {
+ LOG.warn("Transaction chain failed, updates may not have been propagated", cause);
+ txChain = null;
+ }
+
+ @Override
+ public synchronized void onTransactionChainSuccessful(final DOMTransactionChain chain) {
+ LOG.debug("Transaction chain closed successfully");
+ txChain = null;
+ }
+
+ @Override
+ public synchronized void onModelContextUpdated(final EffectiveModelContext newModelContext) {
+ if (reg != null) {
+ LOG.debug("Ignoring model context update");
+ return;
+ }
+
+ if (newModelContext.findModuleStatement(RestconfState.QNAME.getModule()).isPresent()) {
+ writeRestconfState();
+ } else {
+ deleteRestconfState();
+ }
+ }
+
+ @Holding("this")
+ private void deleteRestconfState() {
+ if (!written) {
+ LOG.debug("No state recorded as written, not attempting removal");
+ return;
+ }
+
+ LOG.debug("Removing ietf-restconf-monitoring state");
+ if (txChain == null) {
+ txChain = dataBroker.createMergingTransactionChain(this);
+ }
+
+ final var tx = txChain.newWriteOnlyTransaction();
+ tx.delete(LogicalDatastoreType.OPERATIONAL, PATH);
+ tx.commit().addCallback(new FutureCallback<CommitInfo>() {
+ @Override
+ public void onSuccess(final CommitInfo result) {
+ markUnwritten();
+ }
+
+ @Override
+ public void onFailure(final Throwable cause) {
+ // Ignored, will be reported on the transaction chain
+ }
+ }, MoreExecutors.directExecutor());
+ }
+
+ @Holding("this")
+ private void writeRestconfState() {
+ if (written) {
+ LOG.debug("State recorded as written, not updating it");
+ return;
+ }
+
+ LOG.debug("Updating state of ietf-restconf-monitoring");
+ if (txChain == null) {
+ txChain = dataBroker.createMergingTransactionChain(this);
+ }
+
+ final var tx = txChain.newWriteOnlyTransaction();
+ tx.put(LogicalDatastoreType.OPERATIONAL, PATH, mapCapabilites());
+ tx.commit().addCallback(new FutureCallback<CommitInfo>() {
+ @Override
+ public void onSuccess(final CommitInfo result) {
+ markWritten();
+ }
+
+ @Override
+ public void onFailure(final Throwable cause) {
+ // Ignored, will be reported on the transaction chain
+ }
+ }, MoreExecutors.directExecutor());
+ }
+
+ private synchronized void markWritten() {
+ LOG.debug("State of ietf-restconf-monitoring updated");
+ written = true;
+ }
+
+ private synchronized void markUnwritten() {
+ LOG.debug("State of ietf-restconf-monitoring removed");
+ written = false;
+ }
+
+ /**
+ * Create a {@code restconf-state} container.
+ *
+ * @return A container holding capabilities
+ */
+ @VisibleForTesting
+ static @NonNull LeafSetNode<String> mapCapabilites() {
+ return Builders.<String>orderedLeafSetBuilder()
+ .withNodeIdentifier(CAPABILITY)
+ .withChildValue(DepthParam.capabilityUri().toString())
+ .withChildValue(FieldsParam.capabilityUri().toString())
+ .withChildValue(FilterParam.capabilityUri().toString())
+ .withChildValue(AbstractReplayParam.capabilityUri().toString())
+ .withChildValue(WithDefaultsParam.capabilityUri().toString())
+ .withChildValue(PrettyPrintParam.capabilityUri().toString())
+ .withChildValue(LeafNodesOnlyParam.capabilityUri().toString())
+ .withChildValue(SkipNotificationDataParam.capabilityUri().toString())
+ .build();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2022 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 hosting support for {@code ietf-restconf-monitoring}.
+ */
+package org.opendaylight.restconf.nb.rfc8040.monitoring;
\ No newline at end of file
* 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.restconf.nb.rfc8040.handlers;
+package org.opendaylight.restconf.nb.rfc8040.legacy;
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import java.io.FileNotFoundException;
-import java.util.stream.Collectors;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
-import org.opendaylight.mdsal.common.api.CommitInfo;
import org.opendaylight.mdsal.dom.api.DOMDataBroker;
-import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
import org.opendaylight.mdsal.dom.api.DOMSchemaService;
import org.opendaylight.restconf.nb.rfc8040.TestRestconfUtils;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.monitoring.rev170126.restconf.state.Capabilities;
import org.opendaylight.yangtools.concepts.ListenerRegistration;
-import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
-import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
-import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
*/
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class SchemaContextHandlerTest {
-
- private static final String PATH_FOR_ACTUAL_SCHEMA_CONTEXT = "/modules";
- private static final String PATH_FOR_NEW_SCHEMA_CONTEXT = "/modules/modules-behind-mount-point";
-
private static EffectiveModelContext SCHEMA_CONTEXT;
private SchemaContextHandler schemaContextHandler;
+ @Mock
+ private DOMDataBroker mockDOMDataBroker;
@Mock
private DOMSchemaService mockDOMSchemaService;
@Mock
@BeforeClass
public static void beforeClass() throws FileNotFoundException {
- SCHEMA_CONTEXT = YangParserTestUtils.parseYangFiles(
- TestRestconfUtils.loadFiles(PATH_FOR_ACTUAL_SCHEMA_CONTEXT));
+ SCHEMA_CONTEXT = YangParserTestUtils.parseYangFiles(TestRestconfUtils.loadFiles("/modules"));
}
@AfterClass
@Before
public void setup() throws Exception {
- final DOMDataBroker dataBroker = mock(DOMDataBroker.class);
- final DOMDataTreeWriteTransaction wTx = mock(DOMDataTreeWriteTransaction.class);
- doReturn(wTx).when(dataBroker).newWriteOnlyTransaction();
- doReturn(CommitInfo.emptyFluentFuture()).when(wTx).commit();
-
doReturn(mockListenerReg).when(mockDOMSchemaService).registerSchemaContextListener(any());
- schemaContextHandler = new SchemaContextHandler(dataBroker, mockDOMSchemaService);
+ schemaContextHandler = new SchemaContextHandler(mockDOMDataBroker, mockDOMSchemaService);
verify(mockDOMSchemaService).registerSchemaContextListener(schemaContextHandler);
schemaContextHandler.onModelContextUpdated(SCHEMA_CONTEXT);
public void onGlobalContextUpdateTest() throws Exception {
// create new SchemaContext and update SchemaContextHandler
final EffectiveModelContext newSchemaContext =
- YangParserTestUtils.parseYangFiles(TestRestconfUtils.loadFiles(PATH_FOR_NEW_SCHEMA_CONTEXT));
+ YangParserTestUtils.parseYangFiles(TestRestconfUtils.loadFiles("/modules/modules-behind-mount-point"));
schemaContextHandler.onModelContextUpdated(newSchemaContext);
assertNotEquals("SchemaContextHandler should not has reference to old SchemaContext",
assertEquals("SchemaContextHandler should has reference to new SchemaContext",
newSchemaContext, schemaContextHandler.get());
}
-
- @Test
- public void restconfStateCapabilitesTest() {
- final ContainerNode normNode = SchemaContextHandler.mapCapabilites();
-
- @SuppressWarnings("unchecked")
- final LeafSetNode<String> capability = (LeafSetNode<String>) normNode.body().stream()
- // Find 'capabilities' container
- .filter(child -> Capabilities.QNAME.equals(child.getIdentifier().getNodeType()))
- .findFirst()
- .map(ContainerNode.class::cast)
- .orElseThrow()
- // Find 'capability' leaf-list
- .body().stream()
- .filter(child -> SchemaContextHandler.CAPABILITY_QNAME.equals(child.getIdentifier().getNodeType()))
- .findFirst()
- .orElseThrow();
-
- assertThat(
- capability.body().stream().map(entry -> ((LeafSetEntryNode<?>) entry).body()).collect(Collectors.toList()),
- containsInAnyOrder(
- equalTo("urn:ietf:params:restconf:capability:depth:1.0"),
- equalTo("urn:ietf:params:restconf:capability:fields:1.0"),
- equalTo("urn:ietf:params:restconf:capability:filter:1.0"),
- equalTo("urn:ietf:params:restconf:capability:replay:1.0"),
- equalTo("urn:ietf:params:restconf:capability:with-defaults:1.0"),
- equalTo("urn:opendaylight:params:restconf:capability:pretty-print:1.0"),
- equalTo("urn:opendaylight:params:restconf:capability:leaf-nodes-only:1.0"),
- equalTo("urn:opendaylight:params:restconf:capability:changed-leaf-nodes-only:1.0"),
- equalTo("urn:opendaylight:params:restconf:capability:skip-notification-data:1.0")));
- }
-
}
--- /dev/null
+/*
+ * Copyright (c) 2022 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.restconf.nb.rfc8040.monitoring;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
+
+public class CapabilitiesWriterTest {
+ @Test
+ public void restconfStateCapabilitiesTest() {
+ final var capability = CapabilitiesWriter.mapCapabilites();
+ assertEquals(CapabilitiesWriter.CAPABILITY, capability.name());
+
+ assertThat(capability.body().stream().map(LeafSetEntryNode::body).toList(),
+ containsInAnyOrder(
+ equalTo("urn:ietf:params:restconf:capability:depth:1.0"),
+ equalTo("urn:ietf:params:restconf:capability:fields:1.0"),
+ equalTo("urn:ietf:params:restconf:capability:filter:1.0"),
+ equalTo("urn:ietf:params:restconf:capability:replay:1.0"),
+ equalTo("urn:ietf:params:restconf:capability:with-defaults:1.0"),
+ equalTo("urn:opendaylight:params:restconf:capability:pretty-print:1.0"),
+ equalTo("urn:opendaylight:params:restconf:capability:leaf-nodes-only:1.0"),
+ equalTo("urn:opendaylight:params:restconf:capability:skip-notification-data:1.0")));
+ }
+}
import org.junit.Test;
import org.opendaylight.restconf.nb.rfc8040.Rfc8040.IetfYangLibrary;
import org.opendaylight.restconf.nb.rfc8040.TestRestconfUtils;
-import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler;
+import org.opendaylight.restconf.nb.rfc8040.legacy.SchemaContextHandler;
import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.module.list.module.Deviation;
import org.opendaylight.yangtools.yang.common.QName;