rippled
NodeToShardRPC_test.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2021 Ripple Labs Inc.
5 
6  Permission to use, copy, modify, and/or distribute this software for any
7  purpose with or without fee is hereby granted, provided that the above
8  copyright notice and this permission notice appear in all copies.
9 
10  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 //==============================================================================
19 
20 #include <ripple/beast/unit_test.h>
21 #include <ripple/beast/utility/temp_dir.h>
22 #include <ripple/core/ConfigSections.h>
23 #include <ripple/nodestore/DatabaseShard.h>
24 #include <ripple/protocol/jss.h>
25 #include <test/jtx/Env.h>
26 
27 namespace ripple {
28 namespace test {
29 
30 class NodeToShardRPC_test : public beast::unit_test::suite
31 {
32  bool
34  NodeStore::DatabaseShard* shardStore,
35  std::uint8_t const numberOfShards,
36  Json::Value const& result)
37  {
38  auto const info = shardStore->getShardInfo();
39 
40  // Assume completed if the import isn't running
41  auto const completed =
42  result[jss::error_message] == "Database import not running";
43 
44  if (completed)
45  {
46  BEAST_EXPECT(
47  info->incomplete().size() + info->finalized().size() ==
48  numberOfShards);
49  }
50 
51  return completed;
52  }
53 
54 public:
55  void
57  {
58  testcase("Start");
59 
60  beast::temp_dir tempDir;
61 
62  jtx::Env env = [&] {
63  auto c = jtx::envconfig();
64  auto& section = c->section(ConfigSection::shardDatabase());
65  section.set("path", tempDir.path());
66  section.set("max_historical_shards", "20");
67  section.set("ledgers_per_shard", "256");
68  section.set("earliest_seq", "257");
69  auto& sectionNode = c->section(ConfigSection::nodeDatabase());
70  sectionNode.set("earliest_seq", "257");
71  sectionNode.set("ledgers_per_shard", "256");
72  c->setupControl(true, true, true);
73 
74  return jtx::Env(*this, std::move(c));
75  }();
76 
77  std::uint8_t const numberOfShards = 10;
78 
79  // Create some ledgers so that we can initiate a
80  // shard store database import.
81  for (int i = 0; i < env.app().getShardStore()->ledgersPerShard() *
82  (numberOfShards + 1);
83  ++i)
84  {
85  env.close();
86  }
87 
88  auto shardStore = env.app().getShardStore();
89  if (!BEAST_EXPECT(shardStore))
90  return;
91 
92  {
93  // Initiate a shard store import via the RPC
94  // interface.
95 
96  Json::Value jvParams;
97  jvParams[jss::action] = "start";
98 
99  auto const result = env.rpc(
100  "json", "node_to_shard", to_string(jvParams))[jss::result];
101 
102  BEAST_EXPECT(
103  result[jss::message] == "Database import initiated...");
104  }
105 
106  while (!shardStore->getDatabaseImportSequence())
107  {
108  // Wait until the import starts
110  }
111 
112  {
113  // Verify that the import is in progress with
114  // the node_to_shard status RPC command
115 
116  Json::Value jvParams;
117  jvParams[jss::action] = "status";
118 
119  auto const result = env.rpc(
120  "json", "node_to_shard", to_string(jvParams))[jss::result];
121 
122  BEAST_EXPECT(
123  result[jss::status] == "success" ||
124  importCompleted(shardStore, numberOfShards, result));
125 
126  std::chrono::seconds const maxWait{60};
127  auto const start = std::chrono::system_clock::now();
128 
129  while (true)
130  {
131  // Verify that the status object accurately
132  // reflects import progress.
133 
134  auto const completeShards =
135  shardStore->getShardInfo()->finalized();
136 
137  if (!completeShards.empty())
138  {
139  auto const result = env.rpc(
140  "json",
141  "node_to_shard",
142  to_string(jvParams))[jss::result];
143 
144  if (!importCompleted(shardStore, numberOfShards, result))
145  {
146  BEAST_EXPECT(result[jss::firstShardIndex] == 1);
147  BEAST_EXPECT(result[jss::lastShardIndex] == 10);
148  }
149  }
150 
151  if (boost::icl::contains(completeShards, 1))
152  {
153  auto const result = env.rpc(
154  "json",
155  "node_to_shard",
156  to_string(jvParams))[jss::result];
157 
158  BEAST_EXPECT(
159  result[jss::currentShardIndex] >= 1 ||
160  importCompleted(shardStore, numberOfShards, result));
161 
162  break;
163  }
164 
166  std::chrono::system_clock::now() - start > maxWait)
167  {
168  BEAST_EXPECTS(
169  false, "Import timeout: could just be a slow machine.");
170  break;
171  }
172  }
173 
174  // Wait for the import to complete
175  while (!boost::icl::contains(
176  shardStore->getShardInfo()->finalized(), 10))
177  {
179  std::chrono::system_clock::now() - start > maxWait)
180  {
181  BEAST_EXPECT(
182  importCompleted(shardStore, numberOfShards, result));
183  break;
184  }
185  }
186  }
187  }
188 
189  void
191  {
192  testcase("Stop");
193 
194  beast::temp_dir tempDir;
195 
196  jtx::Env env = [&] {
197  auto c = jtx::envconfig();
198  auto& section = c->section(ConfigSection::shardDatabase());
199  section.set("path", tempDir.path());
200  section.set("max_historical_shards", "20");
201  section.set("ledgers_per_shard", "256");
202  section.set("earliest_seq", "257");
203  auto& sectionNode = c->section(ConfigSection::nodeDatabase());
204  sectionNode.set("earliest_seq", "257");
205  sectionNode.set("ledgers_per_shard", "256");
206  c->setupControl(true, true, true);
207 
208  return jtx::Env(*this, std::move(c));
209  }();
210 
211  std::uint8_t const numberOfShards = 10;
212 
213  // Create some ledgers so that we can initiate a
214  // shard store database import.
215  for (int i = 0; i < env.app().getShardStore()->ledgersPerShard() *
216  (numberOfShards + 1);
217  ++i)
218  {
219  env.close();
220  }
221 
222  auto shardStore = env.app().getShardStore();
223  if (!BEAST_EXPECT(shardStore))
224  return;
225 
226  {
227  // Initiate a shard store import via the RPC
228  // interface.
229 
230  Json::Value jvParams;
231  jvParams[jss::action] = "start";
232 
233  auto const result = env.rpc(
234  "json", "node_to_shard", to_string(jvParams))[jss::result];
235 
236  BEAST_EXPECT(
237  result[jss::message] == "Database import initiated...");
238  }
239 
240  {
241  // Verify that the import is in progress with
242  // the node_to_shard status RPC command
243 
244  Json::Value jvParams;
245  jvParams[jss::action] = "status";
246 
247  auto const result = env.rpc(
248  "json", "node_to_shard", to_string(jvParams))[jss::result];
249 
250  BEAST_EXPECT(
251  result[jss::status] == "success" ||
252  importCompleted(shardStore, numberOfShards, result));
253 
254  std::chrono::seconds const maxWait{10};
255  auto const start = std::chrono::system_clock::now();
256 
257  while (shardStore->getShardInfo()->finalized().empty())
258  {
259  // Wait for at least one shard to complete
260 
262  std::chrono::system_clock::now() - start > maxWait)
263  {
264  BEAST_EXPECTS(
265  false, "Import timeout: could just be a slow machine.");
266  break;
267  }
268  }
269  }
270 
271  {
272  Json::Value jvParams;
273  jvParams[jss::action] = "stop";
274 
275  auto const result = env.rpc(
276  "json", "node_to_shard", to_string(jvParams))[jss::result];
277 
278  BEAST_EXPECT(
279  result[jss::message] == "Database import halt initiated..." ||
280  importCompleted(shardStore, numberOfShards, result));
281  }
282 
283  std::chrono::seconds const maxWait{10};
284  auto const start = std::chrono::system_clock::now();
285 
286  while (true)
287  {
288  // Wait until we can verify that the import has
289  // stopped
290 
291  Json::Value jvParams;
292  jvParams[jss::action] = "status";
293 
294  auto const result = env.rpc(
295  "json", "node_to_shard", to_string(jvParams))[jss::result];
296 
297  // When the import has stopped, polling the
298  // status returns an error
299  if (result.isMember(jss::error))
300  {
301  if (BEAST_EXPECT(result.isMember(jss::error_message)))
302  {
303  BEAST_EXPECT(
304  result[jss::error_message] ==
305  "Database import not running");
306  }
307 
308  break;
309  }
310 
312  std::chrono::system_clock::now() - start > maxWait)
313  {
314  BEAST_EXPECTS(
315  false, "Import timeout: could just be a slow machine.");
316  break;
317  }
318  }
319  }
320 
321  void
322  run() override
323  {
324  testStart();
325  testStop();
326  }
327 };
328 
329 BEAST_DEFINE_TESTSUITE(NodeToShardRPC, rpc, ripple);
330 } // namespace test
331 } // namespace ripple
std::this_thread::sleep_for
T sleep_for(T... args)
ripple::test::NodeToShardRPC_test::testStop
void testStop()
Definition: NodeToShardRPC_test.cpp:190
ripple::ConfigSection::shardDatabase
static std::string shardDatabase()
Definition: ConfigSections.h:38
std::chrono::milliseconds
ripple::Application::getShardStore
virtual NodeStore::DatabaseShard * getShardStore()=0
ripple::test::jtx::Env::app
Application & app()
Definition: Env.h:241
ripple::test::jtx::envconfig
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition: envconfig.h:49
ripple::test::NodeToShardRPC_test::testStart
void testStart()
Definition: NodeToShardRPC_test.cpp:56
ripple::NodeStore::DatabaseShard
A collection of historical shards.
Definition: DatabaseShard.h:37
ripple::test::jtx::Env::close
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:121
ripple::test::NodeToShardRPC_test
Definition: NodeToShardRPC_test.cpp:30
std::uint8_t
beast::temp_dir::path
std::string path() const
Get the native path for the temporary directory.
Definition: temp_dir.h:66
ripple::test::NodeToShardRPC_test::importCompleted
bool importCompleted(NodeStore::DatabaseShard *shardStore, std::uint8_t const numberOfShards, Json::Value const &result)
Definition: NodeToShardRPC_test.cpp:33
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::to_string
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
Definition: app/misc/impl/Manifest.cpp:38
ripple::test::NodeToShardRPC_test::run
void run() override
Definition: NodeToShardRPC_test.cpp:322
ripple::NodeStore::DatabaseShard::getShardInfo
virtual std::unique_ptr< ShardInfo > getShardInfo() const =0
Query information about shards held.
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:116
ripple::ConfigSection::nodeDatabase
static std::string nodeDatabase()
Definition: ConfigSections.h:33
beast::temp_dir
RAII temporary directory.
Definition: temp_dir.h:33
ripple::NodeStore::Database::ledgersPerShard
std::uint32_t ledgersPerShard() const noexcept
Definition: Database.h:229
ripple::test::jtx::Env::rpc
Json::Value rpc(std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:684
Json::Value
Represents a JSON value.
Definition: json_value.h:145
ripple::test::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(DeliverMin, app, ripple)
std::chrono::system_clock::now
T now(T... args)