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.controller.cluster.raft.protobuff.client.messages.Payload;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
29 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
30 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
31 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
32 import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactorySupplier;
33 import org.opendaylight.yangtools.yang.data.codec.gson.JSONNormalizedNodeStreamWriter;
34 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidate;
35 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidateNode;
36 import org.opendaylight.yangtools.yang.data.tree.api.ModificationType;
37 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
38 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
40 public final class JsonExportActor extends AbstractUntypedActor {
42 public static final class ExportSnapshot {
43 private final String id;
45 private final DataTreeCandidate dataTreeCandidate;
47 public ExportSnapshot(final DataTreeCandidate candidate, final String id) {
48 dataTreeCandidate = requireNonNull(candidate);
49 this.id = requireNonNull(id);
53 public static final class ExportJournal {
54 private final ReplicatedLogEntry replicatedLogEntry;
56 public ExportJournal(final ReplicatedLogEntry replicatedLogEntry) {
57 this.replicatedLogEntry = requireNonNull(replicatedLogEntry);
61 public static final class FinishExport {
62 private final String id;
64 public FinishExport(final String id) {
65 this.id = requireNonNull(id);
69 private final List<ReplicatedLogEntry> entries = new ArrayList<>();
70 private final @NonNull EffectiveModelContext schemaContext;
71 private final @NonNull Path baseDirPath;
73 private JsonExportActor(final EffectiveModelContext schemaContext, final Path dirPath) {
74 this.schemaContext = requireNonNull(schemaContext);
75 baseDirPath = requireNonNull(dirPath);
78 public static Props props(final EffectiveModelContext schemaContext, final String dirPath) {
79 return Props.create(JsonExportActor.class, schemaContext, Paths.get(dirPath));
83 protected void handleReceive(final Object message) {
84 if (message instanceof ExportSnapshot) {
85 onExportSnapshot((ExportSnapshot) message);
86 } else if (message instanceof ExportJournal) {
87 onExportJournal((ExportJournal) message);
88 } else if (message instanceof FinishExport) {
89 onFinishExport((FinishExport)message);
91 unknownMessage(message);
95 private void onExportSnapshot(final ExportSnapshot exportSnapshot) {
96 final Path snapshotDir = baseDirPath.resolve("snapshots");
97 createDir(snapshotDir);
99 final Path filePath = snapshotDir.resolve(exportSnapshot.id + "-snapshot.json");
100 LOG.debug("Creating JSON file : {}", filePath);
102 final NormalizedNode root = exportSnapshot.dataTreeCandidate.getRootNode().getDataAfter().get();
103 checkState(root instanceof NormalizedNodeContainer, "Unexpected root %s", root);
105 writeSnapshot(filePath, (NormalizedNodeContainer<?>) root);
106 LOG.debug("Created JSON file: {}", filePath);
109 private void onExportJournal(final ExportJournal exportJournal) {
110 entries.add(exportJournal.replicatedLogEntry);
113 private void onFinishExport(final FinishExport finishExport) {
114 final Path journalDir = baseDirPath.resolve("journals");
115 createDir(journalDir);
117 final Path filePath = journalDir.resolve(finishExport.id + "-journal.json");
118 LOG.debug("Creating JSON file : {}", filePath);
119 writeJournal(filePath);
120 LOG.debug("Created JSON file: {}", filePath);
123 private void writeSnapshot(final Path path, final NormalizedNodeContainer<?> root) {
124 try (JsonWriter jsonWriter = new JsonWriter(Files.newBufferedWriter(path))) {
125 jsonWriter.beginObject();
127 try (NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(
128 JSONNormalizedNodeStreamWriter.createNestedWriter(
129 JSONCodecFactorySupplier.RFC7951.getShared(schemaContext),
130 SchemaInferenceStack.of(schemaContext).toInference(), null, jsonWriter),
132 for (NormalizedNode node : root.body()) {
133 nnWriter.write(node);
137 jsonWriter.endObject();
138 } catch (IOException e) {
139 LOG.error("Failed to export stapshot to {}", path, e);
143 private void writeJournal(final Path path) {
144 try (JsonWriter jsonWriter = new JsonWriter(Files.newBufferedWriter(path))) {
145 jsonWriter.beginObject().name("Entries");
146 jsonWriter.beginArray();
147 for (ReplicatedLogEntry entry : entries) {
148 final Payload data = entry.getData();
149 if (data instanceof CommitTransactionPayload) {
150 final CommitTransactionPayload payload = (CommitTransactionPayload) entry.getData();
151 final DataTreeCandidate candidate = payload.getCandidate().getValue().getCandidate();
152 writeNode(jsonWriter, candidate);
154 jsonWriter.beginObject().name("Payload").value(data.toString()).endObject();
157 jsonWriter.endArray();
158 jsonWriter.endObject();
159 } catch (IOException e) {
160 LOG.error("Failed to export journal to {}", path, e);
164 private static void writeNode(final JsonWriter writer, final DataTreeCandidate candidate) throws IOException {
165 writer.beginObject();
166 writer.name("Entry");
168 doWriteNode(writer, candidate.getRootPath(), candidate.getRootNode());
173 private static void doWriteNode(final JsonWriter writer, final YangInstanceIdentifier path,
174 final DataTreeCandidateNode node) throws IOException {
175 switch (node.getModificationType()) {
178 case SUBTREE_MODIFIED:
179 NodeIterator iterator = new NodeIterator(null, path, node.getChildNodes().iterator());
181 iterator = iterator.next(writer);
182 } while (iterator != null);
187 outputNodeInfo(writer, path, node);
190 outputDefault(writer, path, node);
194 private static void outputNodeInfo(final JsonWriter writer, final YangInstanceIdentifier path,
195 final DataTreeCandidateNode node) throws IOException {
196 final ModificationType modificationType = node.getModificationType();
198 writer.beginObject().name("Node");
200 writer.beginObject().name("Path").value(path.toString()).endObject();
201 writer.beginObject().name("ModificationType").value(modificationType.toString()).endObject();
202 if (modificationType == ModificationType.WRITE) {
203 writer.beginObject().name("Data").value(node.getDataAfter().get().body().toString()).endObject();
209 private static void outputDefault(final JsonWriter writer, final YangInstanceIdentifier path,
210 final DataTreeCandidateNode node) throws IOException {
211 writer.beginObject().name("Node");
213 writer.beginObject().name("Path").value(path.toString()).endObject();
214 writer.beginObject().name("ModificationType")
215 .value("UNSUPPORTED MODIFICATION: " + node.getModificationType()).endObject();
220 private void createDir(final Path path) {
222 Files.createDirectories(path);
223 } catch (IOException e) {
224 LOG.warn("Directory {} cannot be created", path, e);
228 private static final class NodeIterator {
229 private final Iterator<DataTreeCandidateNode> iterator;
230 private final YangInstanceIdentifier path;
231 private final NodeIterator parent;
233 NodeIterator(final @Nullable NodeIterator parent, final YangInstanceIdentifier path,
234 final Iterator<DataTreeCandidateNode> iterator) {
235 this.iterator = requireNonNull(iterator);
236 this.path = requireNonNull(path);
237 this.parent = parent;
240 NodeIterator next(final JsonWriter writer) throws IOException {
241 while (iterator.hasNext()) {
242 final DataTreeCandidateNode node = iterator.next();
243 final YangInstanceIdentifier child = path.node(node.getIdentifier());
245 switch (node.getModificationType()) {
248 case SUBTREE_MODIFIED:
249 return new NodeIterator(this, child, node.getChildNodes().iterator());
253 outputNodeInfo(writer, path, node);
256 outputDefault(writer, child, node);