be6d0cf599168dd8d2bfb9436667a9d6f38cc522
[netconf.git] / apps / yanglib-mdsal-writer / src / main / java / org / opendaylight / netconf / yanglib / writer / YangLibraryWriter.java
1 /*
2  * Copyright (c) 2023 PANTHEON.tech s.r.o. and others. All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.netconf.yanglib.writer;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.util.concurrent.FutureCallback;
13 import com.google.common.util.concurrent.Futures;
14 import com.google.common.util.concurrent.ListenableFuture;
15 import com.google.common.util.concurrent.MoreExecutors;
16 import java.util.concurrent.atomic.AtomicBoolean;
17 import java.util.concurrent.atomic.AtomicLong;
18 import org.eclipse.jdt.annotation.NonNull;
19 import org.opendaylight.mdsal.binding.api.DataBroker;
20 import org.opendaylight.mdsal.binding.api.TransactionChain;
21 import org.opendaylight.mdsal.common.api.CommitInfo;
22 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
23 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
24 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.ModulesState;
25 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.YangLibrary;
26 import org.opendaylight.yangtools.concepts.Registration;
27 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
28 import org.opendaylight.yangtools.yang.common.Empty;
29 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33 /**
34  * Listens for updates on global schema context, transforms context to ietf-yang-library/yang-library and writes this
35  * state to operational data store.
36  */
37 final class YangLibraryWriter implements FutureCallback<Empty> {
38     private static final Logger LOG = LoggerFactory.getLogger(YangLibraryWriter.class);
39     private static final InstanceIdentifier<YangLibrary> YANG_LIBRARY_INSTANCE_IDENTIFIER =
40         InstanceIdentifier.create(YangLibrary.class);
41     @Deprecated
42     private static final InstanceIdentifier<ModulesState> MODULES_STATE_INSTANCE_IDENTIFIER =
43         InstanceIdentifier.create(ModulesState.class);
44
45     private final AtomicLong idCounter = new AtomicLong();
46     private final AtomicBoolean closed = new AtomicBoolean();
47     private final @NonNull YangLibrarySchemaSourceUrlProvider urlProvider;
48     private final DataBroker dataBroker;
49     private final boolean writeLegacy;
50     private final Registration reg;
51
52     // FIXME: this really should be a dynamically-populated shard (i.e. no write operations whatsoever)!
53     private TransactionChain currentChain;
54
55     YangLibraryWriter(final DOMSchemaService schemaService, final DataBroker dataBroker,
56             final boolean writeLegacy, final YangLibrarySchemaSourceUrlProvider urlProvider) {
57         this.dataBroker = requireNonNull(dataBroker);
58         this.urlProvider = requireNonNull(urlProvider);
59         this.writeLegacy = writeLegacy;
60         reg = schemaService.registerSchemaContextListener(this::onModelContextUpdated);
61         LOG.info("ietf-yang-library writer started with modules-state {}", writeLegacy ? "enabled" : "disabled");
62     }
63
64     synchronized ListenableFuture<Empty> shutdown() {
65         if (!closed.compareAndSet(false, true)) {
66             // Already shut down
67             return null;
68         }
69         reg.close();
70
71         final ListenableFuture<Empty> future;
72         try (var chain = acquireChain()) {
73             future = chain.future();
74             final var tx = chain.newWriteOnlyTransaction();
75             tx.delete(LogicalDatastoreType.OPERATIONAL, YANG_LIBRARY_INSTANCE_IDENTIFIER);
76             if (writeLegacy) {
77                 tx.delete(LogicalDatastoreType.OPERATIONAL, MODULES_STATE_INSTANCE_IDENTIFIER);
78             }
79
80             tx.commit().addCallback(new FutureCallback<CommitInfo>() {
81                 @Override
82                 public void onSuccess(final CommitInfo info) {
83                     LOG.debug("ietf-yang-library cleaned successfully");
84                 }
85
86                 @Override
87                 public void onFailure(final Throwable throwable) {
88                     // Handled via transaction chain listener
89                 }
90             }, MoreExecutors.directExecutor());
91         }
92         currentChain = null;
93
94         Futures.addCallback(future, new FutureCallback<>() {
95             @Override
96             public void onSuccess(final Empty value) {
97                 LOG.info("ietf-yang-library writer stopped");
98             }
99
100             @Override
101             public void onFailure(final Throwable throwable) {
102                 LOG.warn("ietf-yang-library writer stopped uncleanly", throwable);
103             }
104         }, MoreExecutors.directExecutor());
105
106         return future;
107     }
108
109     private void onModelContextUpdated(final EffectiveModelContext context) {
110         if (context.findModule(YangLibrary.QNAME.getModule()).isPresent()) {
111             updateYangLibrary(context);
112         } else {
113             LOG.warn("ietf-yang-library not present in context, skipping update");
114         }
115     }
116
117     private synchronized void updateYangLibrary(final EffectiveModelContext context) {
118         if (closed.get()) {
119             // Already shut down, do not do anything
120             LOG.debug("ietf-yang-library writer closed, skipping update");
121             return;
122         }
123
124         final var nextId = String.valueOf(idCounter.incrementAndGet());
125         LOG.debug("ietf-yang-library writer starting update to {}", nextId);
126         final var tx = acquireChain().newWriteOnlyTransaction();
127         tx.put(LogicalDatastoreType.OPERATIONAL, YANG_LIBRARY_INSTANCE_IDENTIFIER,
128             YangLibraryContentBuilderUtil.buildYangLibrary(context, nextId, urlProvider));
129         if (writeLegacy) {
130             tx.put(LogicalDatastoreType.OPERATIONAL, MODULES_STATE_INSTANCE_IDENTIFIER,
131                 YangLibraryContentBuilderUtil.buildModuleState(context, nextId, urlProvider));
132         }
133
134         tx.commit().addCallback(new FutureCallback<CommitInfo>() {
135             @Override
136             public void onSuccess(final CommitInfo result) {
137                 LOG.debug("ietf-yang-library updated successfully");
138             }
139
140             @Override
141             public void onFailure(final Throwable throwable) {
142                 // Handled via transaction chain listener
143             }
144         }, MoreExecutors.directExecutor());
145     }
146
147     private TransactionChain acquireChain() {
148         var local = currentChain;
149         if (local == null) {
150             currentChain = local = dataBroker.createMergingTransactionChain();
151             LOG.debug("Allocated new transaction chain");
152             local.addCallback(this);
153         }
154         return local;
155     }
156
157     @Override
158     public synchronized void onSuccess(final Empty result) {
159         LOG.debug("ietf-yang-library writer transaction chain succeeded");
160         currentChain = null;
161     }
162
163     @Override
164     public synchronized void onFailure(final Throwable cause) {
165         LOG.info("ietf-yang-library writer transaction chain failed", cause);
166         currentChain = null;
167     }
168 }