2 * Copyright (c) 2021 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;
10 import static org.awaitility.Awaitility.await;
11 import static org.junit.Assert.assertEquals;
14 import java.io.IOException;
15 import java.nio.file.Files;
16 import java.nio.file.Path;
17 import java.util.HashSet;
19 import java.util.concurrent.TimeUnit;
20 import org.junit.Before;
21 import org.junit.Rule;
22 import org.junit.Test;
23 import org.junit.rules.TemporaryFolder;
24 import org.opendaylight.controller.cluster.raft.persisted.ApplyJournalEntries;
25 import org.opendaylight.controller.cluster.raft.persisted.SimpleReplicatedLogEntry;
26 import org.opendaylight.controller.cluster.raft.utils.InMemoryJournal;
27 import org.opendaylight.controller.md.cluster.datastore.model.TestModel;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.config.distributed.datastore.provider.rev140612.DataStoreProperties.ExportOnRecovery;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
30 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
31 import org.opendaylight.yangtools.yang.data.tree.api.DataTree;
32 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeModification;
34 public class JsonExportTest extends AbstractShardTest {
35 private static final String DUMMY_DATA = "Dummy data as snapshot sequence number is set to 0 in "
36 + "InMemorySnapshotStore and journal recovery seq number will start from 1";
37 private static final String EXPECTED_JOURNAL_FILE = "expectedJournalExport.json";
38 private static final String EXPECTED_SNAPSHOT_FILE = "expectedSnapshotExport.json";
39 private static String actualJournalFilePath;
40 private static String actualSnapshotFilePath;
41 private DatastoreContext datastoreContext;
44 public TemporaryFolder temporaryFolder = new TemporaryFolder();
48 public void setUp() throws Exception {
50 final File exportTmpFolder = temporaryFolder.newFolder("persistence-export");
51 actualJournalFilePath = exportTmpFolder.getAbsolutePath() + "/journals/"
52 + "member-1-shard-inventory-config" + nextShardNum + "-journal.json";
53 actualSnapshotFilePath = exportTmpFolder.getAbsolutePath() + "/snapshots/"
54 + "member-1-shard-inventory-config" + nextShardNum + "-snapshot.json";
55 datastoreContext = DatastoreContext.newBuilder().shardJournalRecoveryLogBatchSize(1)
56 .shardSnapshotBatchCount(5000).shardHeartbeatIntervalInMillis(HEARTBEAT_MILLIS).persistent(true)
57 .exportOnRecovery(ExportOnRecovery.Json)
58 .recoveryExportBaseDir(exportTmpFolder.getAbsolutePath()).build();
62 protected DatastoreContext newDatastoreContext() {
63 return datastoreContext;
67 public void testJsonExport() throws Exception {
68 // Set up the InMemorySnapshotStore.
69 final DataTree source = setupInMemorySnapshotStore();
71 final DataTreeModification writeMod = source.takeSnapshot().newModification();
72 writeMod.write(TestModel.OUTER_LIST_PATH, ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME).build());
74 InMemoryJournal.addEntry(shardID.toString(), 0, DUMMY_DATA);
76 // Set up the InMemoryJournal.
77 InMemoryJournal.addEntry(shardID.toString(), 1, new SimpleReplicatedLogEntry(0, 1,
78 payloadForModification(source, writeMod, nextTransactionId())));
80 final int nListEntries = 16;
81 final Set<Integer> listEntryKeys = new HashSet<>();
83 // Add some ModificationPayload entries
84 for (int i = 1; i <= nListEntries; i++) {
87 final YangInstanceIdentifier path = YangInstanceIdentifier.builder(TestModel.OUTER_LIST_PATH)
88 .nodeWithKey(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, i).build();
90 final DataTreeModification mod = source.takeSnapshot().newModification();
91 mod.merge(path, ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, i));
94 InMemoryJournal.addEntry(shardID.toString(), i + 1, new SimpleReplicatedLogEntry(i, 1,
95 payloadForModification(source, mod, nextTransactionId())));
98 InMemoryJournal.addEntry(shardID.toString(), nListEntries + 2,
99 new ApplyJournalEntries(nListEntries));
101 testRecovery(listEntryKeys, false);
103 verifyJournalExport();
104 verifySnapshotExport();
107 private static void verifyJournalExport() throws IOException {
108 final String expectedJournalData = readExpectedFile(EXPECTED_JOURNAL_FILE);
109 final String actualJournalData = readActualFile(actualJournalFilePath);
110 assertEquals("Exported journal is not expected ", expectedJournalData, actualJournalData);
113 private static void verifySnapshotExport() throws IOException {
114 final String expectedSnapshotData = readExpectedFile(EXPECTED_SNAPSHOT_FILE);
115 final String actualSnapshotData = readActualFile(actualSnapshotFilePath);
116 assertEquals("Exported snapshot is not expected ", expectedSnapshotData, actualSnapshotData);
119 private static String readExpectedFile(final String filePath) throws IOException {
120 final File exportFile = new File(JsonExportTest.class.getClassLoader().getResource(filePath).getFile());
121 return new String(Files.readAllBytes(Path.of(exportFile.getPath())));
124 private static String readActualFile(final String filePath) throws IOException {
125 final File exportFile = new File(filePath);
126 await().atMost(10, TimeUnit.SECONDS).until(exportFile::exists);
127 return new String(Files.readAllBytes(Path.of(filePath)));