2 * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.controller.cluster.datastore.actors;
10 import static com.google.common.base.Preconditions.checkState;
11 import static java.util.Objects.requireNonNull;
13 import akka.actor.Props;
14 import com.google.gson.stream.JsonWriter;
15 import java.io.IOException;
16 import java.nio.file.Files;
17 import java.nio.file.Path;
18 import java.nio.file.Paths;
19 import java.util.ArrayList;
20 import java.util.Iterator;
21 import java.util.List;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.opendaylight.controller.cluster.common.actor.AbstractUntypedActor;
25 import org.opendaylight.controller.cluster.datastore.persisted.CommitTransactionPayload;
26 import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
28 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
29 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
30 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
31 import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactorySupplier;
32 import org.opendaylight.yangtools.yang.data.codec.gson.JSONNormalizedNodeStreamWriter;
33 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidate;
34 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidateNode;
35 import org.opendaylight.yangtools.yang.data.tree.api.ModificationType;
36 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
37 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
39 public final class JsonExportActor extends AbstractUntypedActor {
41 public static final class ExportSnapshot {
42 private final String id;
44 private final DataTreeCandidate dataTreeCandidate;
46 public ExportSnapshot(final DataTreeCandidate candidate, final String id) {
47 dataTreeCandidate = requireNonNull(candidate);
48 this.id = requireNonNull(id);
52 public static final class ExportJournal {
53 private final ReplicatedLogEntry replicatedLogEntry;
55 public ExportJournal(final ReplicatedLogEntry replicatedLogEntry) {
56 this.replicatedLogEntry = requireNonNull(replicatedLogEntry);
60 public static final class FinishExport {
61 private final String id;
63 public FinishExport(final String id) {
64 this.id = requireNonNull(id);
68 private final List<ReplicatedLogEntry> entries = new ArrayList<>();
69 private final @NonNull EffectiveModelContext schemaContext;
70 private final @NonNull Path baseDirPath;
72 private JsonExportActor(final EffectiveModelContext schemaContext, final Path dirPath) {
73 this.schemaContext = requireNonNull(schemaContext);
74 baseDirPath = requireNonNull(dirPath);
77 public static Props props(final EffectiveModelContext schemaContext, final String dirPath) {
78 return Props.create(JsonExportActor.class, schemaContext, Paths.get(dirPath));
82 protected void handleReceive(final Object message) {
83 if (message instanceof ExportSnapshot) {
84 onExportSnapshot((ExportSnapshot) message);
85 } else if (message instanceof ExportJournal) {
86 onExportJournal((ExportJournal) message);
87 } else if (message instanceof FinishExport) {
88 onFinishExport((FinishExport)message);
90 unknownMessage(message);
94 private void onExportSnapshot(final ExportSnapshot exportSnapshot) {
95 final Path snapshotDir = baseDirPath.resolve("snapshots");
96 createDir(snapshotDir);
98 final Path filePath = snapshotDir.resolve(exportSnapshot.id + "-snapshot.json");
99 LOG.debug("Creating JSON file : {}", filePath);
101 final NormalizedNode root = exportSnapshot.dataTreeCandidate.getRootNode().getDataAfter().orElseThrow();
102 checkState(root instanceof NormalizedNodeContainer, "Unexpected root %s", root);
104 writeSnapshot(filePath, (NormalizedNodeContainer<?>) root);
105 LOG.debug("Created JSON file: {}", filePath);
108 private void onExportJournal(final ExportJournal exportJournal) {
109 entries.add(exportJournal.replicatedLogEntry);
112 private void onFinishExport(final FinishExport finishExport) {
113 final Path journalDir = baseDirPath.resolve("journals");
114 createDir(journalDir);
116 final Path filePath = journalDir.resolve(finishExport.id + "-journal.json");
117 LOG.debug("Creating JSON file : {}", filePath);
118 writeJournal(filePath);
119 LOG.debug("Created JSON file: {}", filePath);
122 private void writeSnapshot(final Path path, final NormalizedNodeContainer<?> root) {
123 try (JsonWriter jsonWriter = new JsonWriter(Files.newBufferedWriter(path))) {
124 jsonWriter.beginObject();
126 try (NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(
127 JSONNormalizedNodeStreamWriter.createNestedWriter(
128 JSONCodecFactorySupplier.RFC7951.getShared(schemaContext),
129 SchemaInferenceStack.of(schemaContext).toInference(), null, jsonWriter),
131 for (NormalizedNode node : root.body()) {
132 nnWriter.write(node);
136 jsonWriter.endObject();
137 } catch (IOException e) {
138 LOG.error("Failed to export stapshot to {}", path, e);
142 private void writeJournal(final Path path) {
143 try (JsonWriter jsonWriter = new JsonWriter(Files.newBufferedWriter(path))) {
144 jsonWriter.beginObject().name("Entries");
145 jsonWriter.beginArray();
146 for (ReplicatedLogEntry entry : entries) {
147 final var data = entry.getData();
148 if (data instanceof CommitTransactionPayload) {
149 final CommitTransactionPayload payload = (CommitTransactionPayload) entry.getData();
150 final DataTreeCandidate candidate = payload.getCandidate().getValue().getCandidate();
151 writeNode(jsonWriter, candidate);
153 jsonWriter.beginObject().name("Payload").value(data.toString()).endObject();
156 jsonWriter.endArray();
157 jsonWriter.endObject();
158 } catch (IOException e) {
159 LOG.error("Failed to export journal to {}", path, e);
163 private static void writeNode(final JsonWriter writer, final DataTreeCandidate candidate) throws IOException {
164 writer.beginObject();
165 writer.name("Entry");
167 doWriteNode(writer, candidate.getRootPath(), candidate.getRootNode());
172 private static void doWriteNode(final JsonWriter writer, final YangInstanceIdentifier path,
173 final DataTreeCandidateNode node) throws IOException {
174 switch (node.getModificationType()) {
177 case SUBTREE_MODIFIED:
178 NodeIterator iterator = new NodeIterator(null, path, node.getChildNodes().iterator());
180 iterator = iterator.next(writer);
181 } while (iterator != null);
186 outputNodeInfo(writer, path, node);
189 outputDefault(writer, path, node);
193 private static void outputNodeInfo(final JsonWriter writer, final YangInstanceIdentifier path,
194 final DataTreeCandidateNode node) throws IOException {
195 final ModificationType modificationType = node.getModificationType();
197 writer.beginObject().name("Node");
199 writer.beginObject().name("Path").value(path.toString()).endObject();
200 writer.beginObject().name("ModificationType").value(modificationType.toString()).endObject();
201 if (modificationType == ModificationType.WRITE) {
202 writer.beginObject().name("Data").value(node.getDataAfter().orElseThrow().body().toString()).endObject();
208 private static void outputDefault(final JsonWriter writer, final YangInstanceIdentifier path,
209 final DataTreeCandidateNode node) throws IOException {
210 writer.beginObject().name("Node");
212 writer.beginObject().name("Path").value(path.toString()).endObject();
213 writer.beginObject().name("ModificationType")
214 .value("UNSUPPORTED MODIFICATION: " + node.getModificationType()).endObject();
219 private void createDir(final Path path) {
221 Files.createDirectories(path);
222 } catch (IOException e) {
223 LOG.warn("Directory {} cannot be created", path, e);
227 private static final class NodeIterator {
228 private final Iterator<DataTreeCandidateNode> iterator;
229 private final YangInstanceIdentifier path;
230 private final NodeIterator parent;
232 NodeIterator(final @Nullable NodeIterator parent, final YangInstanceIdentifier path,
233 final Iterator<DataTreeCandidateNode> iterator) {
234 this.iterator = requireNonNull(iterator);
235 this.path = requireNonNull(path);
236 this.parent = parent;
239 NodeIterator next(final JsonWriter writer) throws IOException {
240 while (iterator.hasNext()) {
241 final DataTreeCandidateNode node = iterator.next();
242 final YangInstanceIdentifier child = path.node(node.getIdentifier());
244 switch (node.getModificationType()) {
247 case SUBTREE_MODIFIED:
248 return new NodeIterator(this, child, node.getChildNodes().iterator());
252 outputNodeInfo(writer, path, node);
255 outputDefault(writer, child, node);