Move {Identifiable,Persistent,}Payload
[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.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;
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             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 @NonNull EffectiveModelContext schemaContext;
70     private final @NonNull Path baseDirPath;
71
72     private JsonExportActor(final EffectiveModelContext schemaContext, final Path dirPath) {
73         this.schemaContext = requireNonNull(schemaContext);
74         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),
129                     SchemaInferenceStack.of(schemaContext).toInference(), null, jsonWriter),
130                 true)) {
131                 for (NormalizedNode node : root.body()) {
132                     nnWriter.write(node);
133                 }
134             }
135
136             jsonWriter.endObject();
137         } catch (IOException e) {
138             LOG.error("Failed to export stapshot to {}", path, e);
139         }
140     }
141
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);
152                 } else {
153                     jsonWriter.beginObject().name("Payload").value(data.toString()).endObject();
154                 }
155             }
156             jsonWriter.endArray();
157             jsonWriter.endObject();
158         } catch (IOException e) {
159             LOG.error("Failed to export journal to {}", path, e);
160         }
161     }
162
163     private static void writeNode(final JsonWriter writer, final DataTreeCandidate candidate) throws IOException {
164         writer.beginObject();
165         writer.name("Entry");
166         writer.beginArray();
167         doWriteNode(writer, candidate.getRootPath(), candidate.getRootNode());
168         writer.endArray();
169         writer.endObject();
170     }
171
172     private static void doWriteNode(final JsonWriter writer, final YangInstanceIdentifier path,
173             final DataTreeCandidateNode node) throws IOException {
174         switch (node.getModificationType()) {
175             case APPEARED:
176             case DISAPPEARED:
177             case SUBTREE_MODIFIED:
178                 NodeIterator iterator = new NodeIterator(null, path, node.getChildNodes().iterator());
179                 do {
180                     iterator = iterator.next(writer);
181                 } while (iterator != null);
182                 break;
183             case DELETE:
184             case UNMODIFIED:
185             case WRITE:
186                 outputNodeInfo(writer, path, node);
187                 break;
188             default:
189                 outputDefault(writer, path, node);
190         }
191     }
192
193     private static void outputNodeInfo(final JsonWriter writer, final YangInstanceIdentifier path,
194                                        final DataTreeCandidateNode node) throws IOException {
195         final ModificationType modificationType = node.getModificationType();
196
197         writer.beginObject().name("Node");
198         writer.beginArray();
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().get().body().toString()).endObject();
203         }
204         writer.endArray();
205         writer.endObject();
206     }
207
208     private static void outputDefault(final JsonWriter writer, final YangInstanceIdentifier path,
209                                       final DataTreeCandidateNode node) throws IOException {
210         writer.beginObject().name("Node");
211         writer.beginArray();
212         writer.beginObject().name("Path").value(path.toString()).endObject();
213         writer.beginObject().name("ModificationType")
214                 .value("UNSUPPORTED MODIFICATION: " + node.getModificationType()).endObject();
215         writer.endArray();
216         writer.endObject();
217     }
218
219     private void createDir(final Path path) {
220         try {
221             Files.createDirectories(path);
222         } catch (IOException e) {
223             LOG.warn("Directory {} cannot be created", path, e);
224         }
225     }
226
227     private static final class NodeIterator {
228         private final Iterator<DataTreeCandidateNode> iterator;
229         private final YangInstanceIdentifier path;
230         private final NodeIterator parent;
231
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;
237         }
238
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());
243
244                 switch (node.getModificationType()) {
245                     case APPEARED:
246                     case DISAPPEARED:
247                     case SUBTREE_MODIFIED:
248                         return new NodeIterator(this, child, node.getChildNodes().iterator());
249                     case DELETE:
250                     case UNMODIFIED:
251                     case WRITE:
252                         outputNodeInfo(writer, path, node);
253                         break;
254                     default:
255                         outputDefault(writer, child, node);
256                 }
257             }
258
259             return parent;
260         }
261     }
262 }