Standalone yang library data writer
[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.MoreExecutors;
14 import java.util.concurrent.ExecutionException;
15 import java.util.concurrent.atomic.AtomicLong;
16 import javax.annotation.PreDestroy;
17 import javax.inject.Inject;
18 import javax.inject.Singleton;
19 import org.checkerframework.checker.lock.qual.GuardedBy;
20 import org.opendaylight.mdsal.binding.api.DataBroker;
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.model.api.EffectiveModelContext;
29 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContextListener;
30 import org.osgi.service.component.annotations.Activate;
31 import org.osgi.service.component.annotations.Component;
32 import org.osgi.service.component.annotations.Deactivate;
33 import org.osgi.service.component.annotations.Reference;
34 import org.osgi.service.component.annotations.ReferenceCardinality;
35 import org.osgi.service.metatype.annotations.AttributeDefinition;
36 import org.osgi.service.metatype.annotations.Designate;
37 import org.osgi.service.metatype.annotations.ObjectClassDefinition;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 /**
42  * Listens for updates on global schema context, transforms context to ietf-yang-library/yang-library and writes this
43  * state to operational data store.
44  */
45 @Singleton
46 @Component(immediate = true, configurationPid = "org.opendaylight.netconf.yanglib")
47 @Designate(ocd = YangLibraryWriter.Configuration.class)
48 public final class YangLibraryWriter implements EffectiveModelContextListener, AutoCloseable {
49
50     @ObjectClassDefinition
51     public @interface Configuration {
52         @AttributeDefinition(description = "Enables legacy content to be written")
53         boolean write$_$legacy() default false;
54     }
55
56     private static final Logger LOG = LoggerFactory.getLogger(YangLibraryWriter.class);
57     private static final InstanceIdentifier<YangLibrary> YANG_LIBRARY_INSTANCE_IDENTIFIER =
58         InstanceIdentifier.create(YangLibrary.class);
59     private static final InstanceIdentifier<ModulesState> MODULES_STATE_INSTANCE_IDENTIFIER =
60         InstanceIdentifier.create(ModulesState.class);
61
62     private final AtomicLong idCounter = new AtomicLong(0L);
63     private final DataBroker dataBroker;
64     private final boolean writeLegacy;
65
66     @Reference(cardinality = ReferenceCardinality.OPTIONAL)
67     volatile YangLibrarySchemaSourceUrlProvider schemaSourceUrlProvider;
68
69     @GuardedBy("this")
70     private Registration reg;
71
72     @Inject
73     @Activate
74     public YangLibraryWriter(final @Reference DOMSchemaService schemaService,
75         final @Reference DataBroker dataBroker, final Configuration configuration) {
76         this.dataBroker = requireNonNull(dataBroker);
77         this.writeLegacy = configuration.write$_$legacy();
78         reg = schemaService.registerSchemaContextListener(this);
79     }
80
81     @Deactivate
82     @PreDestroy
83     @Override
84     public synchronized void close() throws InterruptedException, ExecutionException {
85         if (reg == null) {
86             // Already shut down
87             return;
88         }
89         reg.close();
90         reg = null;
91
92         // FIXME: we should be using a transaction chain for this, but, really, this should be a dynamically-populated
93         //        shard (i.e. no storage whatsoever)!
94         final var tx = dataBroker.newWriteOnlyTransaction();
95         tx.delete(LogicalDatastoreType.OPERATIONAL, YANG_LIBRARY_INSTANCE_IDENTIFIER);
96         if (writeLegacy) {
97             tx.delete(LogicalDatastoreType.OPERATIONAL, MODULES_STATE_INSTANCE_IDENTIFIER);
98         }
99
100         final var future = tx.commit();
101         future.addCallback(new FutureCallback<CommitInfo>() {
102             @Override
103             public void onSuccess(final CommitInfo info) {
104                 LOG.debug("YANG library cleared successfully");
105             }
106
107             @Override
108             public void onFailure(final Throwable throwable) {
109                 LOG.warn("Unable to clear YANG library", throwable);
110             }
111         }, MoreExecutors.directExecutor());
112
113         // We need to synchronize here, otherwise we'd end up trampling over ourselves
114         future.get();
115     }
116
117     @Override
118     public void onModelContextUpdated(final EffectiveModelContext context) {
119         if (context.findModule(YangLibrary.QNAME.getModule()).isPresent()) {
120             updateYangLibrary(context);
121         } else {
122             LOG.warn("ietf-yang-library not present in context, skipping update");
123         }
124     }
125
126     private synchronized void updateYangLibrary(final EffectiveModelContext context) {
127         if (reg == null) {
128             // Already shut down, do not do anything
129             return;
130         }
131         final var nextId = String.valueOf(idCounter.incrementAndGet());
132         final var tx = dataBroker.newWriteOnlyTransaction();
133         tx.put(LogicalDatastoreType.OPERATIONAL, YANG_LIBRARY_INSTANCE_IDENTIFIER,
134             YangLibraryContentBuilderUtil.buildYangLibrary(context, nextId, schemaSourceUrlProvider));
135         if (writeLegacy) {
136             tx.put(LogicalDatastoreType.OPERATIONAL, MODULES_STATE_INSTANCE_IDENTIFIER,
137                 YangLibraryContentBuilderUtil.buildModuleState(context, nextId, schemaSourceUrlProvider));
138         }
139
140         tx.commit().addCallback(new FutureCallback<CommitInfo>() {
141             @Override
142             public void onSuccess(final CommitInfo result) {
143                 LOG.debug("Yang library updated successfully");
144             }
145
146             @Override
147             public void onFailure(final Throwable throwable) {
148                 LOG.warn("Failed to update yang library", throwable);
149             }
150         }, MoreExecutors.directExecutor());
151     }
152 }