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.Nullable;
23 import org.opendaylight.controller.cluster.common.actor.AbstractUntypedActor;
24 import org.opendaylight.controller.cluster.datastore.persisted.CommitTransactionPayload;
25 import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry;
26 import org.opendaylight.controller.cluster.raft.protobuff.client.messages.Payload;
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.api.schema.tree.DataTreeCandidate;
32 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
34 import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactorySupplier;
35 import org.opendaylight.yangtools.yang.data.codec.gson.JSONNormalizedNodeStreamWriter;
36 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
37 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
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 this.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 EffectiveModelContext schemaContext;
70 private final Path baseDirPath;
72 private JsonExportActor(final EffectiveModelContext schemaContext, final Path dirPath) {
73 this.schemaContext = requireNonNull(schemaContext);
74 this.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().get();
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), SchemaPath.ROOT, null, jsonWriter),
130 for (NormalizedNode node : root.body()) {
131 nnWriter.write(node);
135 jsonWriter.endObject();
136 } catch (IOException e) {
137 LOG.error("Failed to export stapshot to {}", path, e);
141 private void writeJournal(final Path path) {
142 try (JsonWriter jsonWriter = new JsonWriter(Files.newBufferedWriter(path))) {
143 jsonWriter.beginObject().name("Entries");
144 jsonWriter.beginArray();
145 for (ReplicatedLogEntry entry : entries) {
146 final Payload data = entry.getData();
147 if (data instanceof CommitTransactionPayload) {
148 final CommitTransactionPayload payload = (CommitTransactionPayload) entry.getData();
149 final DataTreeCandidate candidate = payload.getCandidate().getValue().getCandidate();
150 writeNode(jsonWriter, candidate);
152 jsonWriter.beginObject().name("Payload").value(data.toString()).endObject();
155 jsonWriter.endArray();
156 jsonWriter.endObject();
157 } catch (IOException e) {
158 LOG.error("Failed to export journal to {}", path, e);
162 private static void writeNode(final JsonWriter writer, final DataTreeCandidate candidate) throws IOException {
163 writer.beginObject();
164 writer.name("Entry");
166 doWriteNode(writer, candidate.getRootPath(), candidate.getRootNode());
171 private static void doWriteNode(final JsonWriter writer, final YangInstanceIdentifier path,
172 final DataTreeCandidateNode node) throws IOException {
173 switch (node.getModificationType()) {
176 case SUBTREE_MODIFIED:
177 NodeIterator iterator = new NodeIterator(null, path, node.getChildNodes().iterator());
179 iterator = iterator.next(writer);
180 } while (iterator != null);
185 outputNodeInfo(writer, path, node);
188 outputDefault(writer, path, node);
192 private static void outputNodeInfo(final JsonWriter writer, final YangInstanceIdentifier path,
193 final DataTreeCandidateNode node) throws IOException {
194 final ModificationType modificationType = node.getModificationType();
196 writer.beginObject().name("Node");
198 writer.beginObject().name("Path").value(path.toString()).endObject();
199 writer.beginObject().name("ModificationType").value(modificationType.toString()).endObject();
200 if (modificationType == ModificationType.WRITE) {
201 writer.beginObject().name("Data").value(node.getDataAfter().get().body().toString()).endObject();
207 private static void outputDefault(final JsonWriter writer, final YangInstanceIdentifier path,
208 final DataTreeCandidateNode node) throws IOException {
209 writer.beginObject().name("Node");
211 writer.beginObject().name("Path").value(path.toString()).endObject();
212 writer.beginObject().name("ModificationType")
213 .value("UNSUPPORTED MODIFICATION: " + node.getModificationType()).endObject();
218 private void createDir(final Path path) {
220 Files.createDirectories(path);
221 } catch (IOException e) {
222 LOG.warn("Directory {} cannot be created", path, e);
226 private static final class NodeIterator {
227 private final Iterator<DataTreeCandidateNode> iterator;
228 private final YangInstanceIdentifier path;
229 private final NodeIterator parent;
231 NodeIterator(final @Nullable NodeIterator parent, final YangInstanceIdentifier path,
232 final Iterator<DataTreeCandidateNode> iterator) {
233 this.iterator = requireNonNull(iterator);
234 this.path = requireNonNull(path);
235 this.parent = parent;
238 NodeIterator next(final JsonWriter writer) throws IOException {
239 while (iterator.hasNext()) {
240 final DataTreeCandidateNode node = iterator.next();
241 final YangInstanceIdentifier child = path.node(node.getIdentifier());
243 switch (node.getModificationType()) {
246 case SUBTREE_MODIFIED:
247 return new NodeIterator(this, child, node.getChildNodes().iterator());
251 outputNodeInfo(writer, path, node);
254 outputDefault(writer, child, node);