369fc0bfbaaa0ce81d8a01ba34f56cfb05567c4e
[controller.git] / opendaylight / md-sal / sal-distributed-datastore / src / main / java / org / opendaylight / controller / cluster / datastore / actors / JsonExportActor.java
1 /*
2  * Copyright (c) 2020 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.controller.cluster.datastore.actors;
9
10 import static com.google.common.base.Preconditions.checkState;
11 import static java.util.Objects.requireNonNull;
12
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;
38
39 public final class JsonExportActor extends AbstractUntypedActor {
40     // Internal messages
41     public static final class ExportSnapshot {
42         private final String id;
43
44         private final DataTreeCandidate dataTreeCandidate;
45
46         public ExportSnapshot(final DataTreeCandidate candidate, final String id) {
47             this.dataTreeCandidate = requireNonNull(candidate);
48             this.id = requireNonNull(id);
49         }
50     }
51
52     public static final class ExportJournal {
53         private final ReplicatedLogEntry replicatedLogEntry;
54
55         public ExportJournal(final ReplicatedLogEntry replicatedLogEntry) {
56             this.replicatedLogEntry = requireNonNull(replicatedLogEntry);
57         }
58     }
59
60     public static final class FinishExport {
61         private final String id;
62
63         public FinishExport(final String id) {
64             this.id = requireNonNull(id);
65         }
66     }
67
68     private final List<ReplicatedLogEntry> entries = new ArrayList<>();
69     private final EffectiveModelContext schemaContext;
70     private final Path baseDirPath;
71
72     private JsonExportActor(final EffectiveModelContext schemaContext, final Path dirPath) {
73         this.schemaContext = requireNonNull(schemaContext);
74         this.baseDirPath = requireNonNull(dirPath);
75     }
76
77     public static Props props(final EffectiveModelContext schemaContext, final String dirPath) {
78         return Props.create(JsonExportActor.class, schemaContext, Paths.get(dirPath));
79     }
80
81     @Override
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);
89         } else {
90             unknownMessage(message);
91         }
92     }
93
94     private void onExportSnapshot(final ExportSnapshot exportSnapshot) {
95         final Path snapshotDir = baseDirPath.resolve("snapshots");
96         createDir(snapshotDir);
97
98         final Path filePath = snapshotDir.resolve(exportSnapshot.id + "-snapshot.json");
99         LOG.debug("Creating JSON file : {}", filePath);
100
101         final NormalizedNode root = exportSnapshot.dataTreeCandidate.getRootNode().getDataAfter().get();
102         checkState(root instanceof NormalizedNodeContainer, "Unexpected root %s", root);
103
104         writeSnapshot(filePath, (NormalizedNodeContainer<?>) root);
105         LOG.debug("Created JSON file: {}", filePath);
106     }
107
108     private void onExportJournal(final ExportJournal exportJournal) {
109         entries.add(exportJournal.replicatedLogEntry);
110     }
111
112     private void onFinishExport(final FinishExport finishExport) {
113         final Path journalDir = baseDirPath.resolve("journals");
114         createDir(journalDir);
115
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);
120     }
121
122     private void writeSnapshot(final Path path, final NormalizedNodeContainer<?> root) {
123         try (JsonWriter jsonWriter = new JsonWriter(Files.newBufferedWriter(path))) {
124             jsonWriter.beginObject();
125
126             try (NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(
127                 JSONNormalizedNodeStreamWriter.createNestedWriter(
128                     JSONCodecFactorySupplier.RFC7951.getShared(schemaContext), SchemaPath.ROOT, null, jsonWriter),
129                 true)) {
130                 for (NormalizedNode node : root.body()) {
131                     nnWriter.write(node);
132                 }
133             }
134
135             jsonWriter.endObject();
136         } catch (IOException e) {
137             LOG.error("Failed to export stapshot to {}", path, e);
138         }
139     }
140
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);
151                 } else {
152                     jsonWriter.beginObject().name("Payload").value(data.toString()).endObject();
153                 }
154             }
155             jsonWriter.endArray();
156             jsonWriter.endObject();
157         } catch (IOException e) {
158             LOG.error("Failed to export journal to {}", path, e);
159         }
160     }
161
162     private static void writeNode(final JsonWriter writer, final DataTreeCandidate candidate) throws IOException {
163         writer.beginObject();
164         writer.name("Entry");
165         writer.beginArray();
166         doWriteNode(writer, candidate.getRootPath(), candidate.getRootNode());
167         writer.endArray();
168         writer.endObject();
169     }
170
171     private static void doWriteNode(final JsonWriter writer, final YangInstanceIdentifier path,
172             final DataTreeCandidateNode node) throws IOException {
173         switch (node.getModificationType()) {
174             case APPEARED:
175             case DISAPPEARED:
176             case SUBTREE_MODIFIED:
177                 NodeIterator iterator = new NodeIterator(null, path, node.getChildNodes().iterator());
178                 do {
179                     iterator = iterator.next(writer);
180                 } while (iterator != null);
181                 break;
182             case DELETE:
183             case UNMODIFIED:
184             case WRITE:
185                 outputNodeInfo(writer, path, node);
186                 break;
187             default:
188                 outputDefault(writer, path, node);
189         }
190     }
191
192     private static void outputNodeInfo(final JsonWriter writer, final YangInstanceIdentifier path,
193                                        final DataTreeCandidateNode node) throws IOException {
194         final ModificationType modificationType = node.getModificationType();
195
196         writer.beginObject().name("Node");
197         writer.beginArray();
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();
202         }
203         writer.endArray();
204         writer.endObject();
205     }
206
207     private static void outputDefault(final JsonWriter writer, final YangInstanceIdentifier path,
208                                       final DataTreeCandidateNode node) throws IOException {
209         writer.beginObject().name("Node");
210         writer.beginArray();
211         writer.beginObject().name("Path").value(path.toString()).endObject();
212         writer.beginObject().name("ModificationType")
213                 .value("UNSUPPORTED MODIFICATION: " + node.getModificationType()).endObject();
214         writer.endArray();
215         writer.endObject();
216     }
217
218     private void createDir(final Path path) {
219         try {
220             Files.createDirectories(path);
221         } catch (IOException e) {
222             LOG.warn("Directory {} cannot be created", path, e);
223         }
224     }
225
226     private static final class NodeIterator {
227         private final Iterator<DataTreeCandidateNode> iterator;
228         private final YangInstanceIdentifier path;
229         private final NodeIterator parent;
230
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;
236         }
237
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());
242
243                 switch (node.getModificationType()) {
244                     case APPEARED:
245                     case DISAPPEARED:
246                     case SUBTREE_MODIFIED:
247                         return new NodeIterator(this, child, node.getChildNodes().iterator());
248                     case DELETE:
249                     case UNMODIFIED:
250                     case WRITE:
251                         outputNodeInfo(writer, path, node);
252                         break;
253                     default:
254                         outputDefault(writer, child, node);
255                 }
256             }
257
258             return parent;
259         }
260     }
261 }