org.opendaylight.restconf.server.mdsal.CapabilitiesWriter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of restconf-server-mdsal Show documentation
Show all versions of restconf-server-mdsal Show documentation
RESTCONF server implemented on top of MD-SAL
/*
* 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.server.mdsal;
import static java.util.Objects.requireNonNull;
import static org.opendaylight.yang.svc.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.DOMSchemaService;
import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
import org.opendaylight.restconf.api.query.AbstractReplayParam;
import org.opendaylight.restconf.api.query.ChangedLeafNodesOnlyParam;
import org.opendaylight.restconf.api.query.ChildNodesOnlyParam;
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.common.Empty;
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.spi.node.ImmutableNodes;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
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, FutureCallback {
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::onModelContextUpdated);
}
@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 onFailure(final Throwable cause) {
LOG.warn("Transaction chain failed, updates may not have been propagated", cause);
txChain = null;
}
@Override
public synchronized void onSuccess(final Empty result) {
LOG.debug("Transaction chain closed successfully");
txChain = null;
}
private 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();
txChain.addCallback(this);
}
final var tx = txChain.newWriteOnlyTransaction();
tx.delete(LogicalDatastoreType.OPERATIONAL, PATH);
tx.commit().addCallback(new FutureCallback() {
@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();
txChain.addCallback(this);
}
final var tx = txChain.newWriteOnlyTransaction();
tx.put(LogicalDatastoreType.OPERATIONAL, PATH, mapCapabilities());
tx.commit().addCallback(new FutureCallback() {
@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 mapCapabilities() {
return ImmutableNodes.newSystemLeafSetBuilder()
.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(ChangedLeafNodesOnlyParam.capabilityUri().toString())
.withChildValue(SkipNotificationDataParam.capabilityUri().toString())
.withChildValue(ChildNodesOnlyParam.capabilityUri().toString())
.build();
}
}