/Users/deen/code/yugabyte-db/ent/src/yb/tools/yb-backup-test_ent.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) YugaByte, Inc. |
2 | | // |
3 | | // |
4 | | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
5 | | // in compliance with the License. You may obtain a copy of the License at |
6 | | // |
7 | | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software distributed under the License |
10 | | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
11 | | // or implied. See the License for the specific language governing permissions and limitations |
12 | | // under the License. |
13 | | |
14 | | // Tests for the yb_backup.py script. |
15 | | |
16 | | #include "yb/yql/pgwrapper/pg_wrapper_test_base.h" |
17 | | |
18 | | #include "yb/common/partition.h" |
19 | | #include "yb/common/redis_constants_common.h" |
20 | | #include "yb/common/redis_protocol.pb.h" |
21 | | #include "yb/common/wire_protocol-test-util.h" |
22 | | |
23 | | #include "yb/client/client-test-util.h" |
24 | | #include "yb/client/schema.h" |
25 | | #include "yb/client/session.h" |
26 | | #include "yb/client/table.h" |
27 | | #include "yb/client/table_creator.h" |
28 | | #include "yb/client/ql-dml-test-base.h" |
29 | | #include "yb/client/yb_op.h" |
30 | | |
31 | | #include "yb/gutil/casts.h" |
32 | | #include "yb/gutil/strings/split.h" |
33 | | |
34 | | #include "yb/master/master_admin.proxy.h" |
35 | | #include "yb/master/master_client.pb.h" |
36 | | #include "yb/rpc/rpc_controller.h" |
37 | | #include "yb/tools/tools_test_utils.h" |
38 | | #include "yb/util/format.h" |
39 | | #include "yb/util/jsonreader.h" |
40 | | #include "yb/util/random_util.h" |
41 | | #include "yb/util/status_format.h" |
42 | | #include "yb/util/subprocess.h" |
43 | | #include "yb/util/tsan_util.h" |
44 | | |
45 | | #include "yb/yql/redis/redisserver/redis_parser.h" |
46 | | |
47 | | using namespace std::chrono_literals; |
48 | | using std::unique_ptr; |
49 | | using std::vector; |
50 | | using std::string; |
51 | | using strings::Split; |
52 | | |
53 | | |
54 | | namespace yb { |
55 | | namespace tools { |
56 | | |
57 | | namespace helpers { |
58 | | YB_DEFINE_ENUM(TableOp, (kKeepTable)(kDropTable)(kDropDB)); |
59 | | |
60 | | CHECKED_STATUS RedisGet(std::shared_ptr<client::YBSession> session, |
61 | | const std::shared_ptr<client::YBTable> table, |
62 | | const string& key, |
63 | 0 | const string& value) { |
64 | 0 | auto get_op = std::make_shared<client::YBRedisReadOp>(table); |
65 | 0 | RETURN_NOT_OK(redisserver::ParseGet(get_op.get(), redisserver::RedisClientCommand({"get", key}))); |
66 | 0 | RETURN_NOT_OK(session->ReadSync(get_op)); |
67 | 0 | if (get_op->response().code() != RedisResponsePB_RedisStatusCode_OK) { |
68 | 0 | return STATUS_FORMAT(RuntimeError, |
69 | 0 | "Redis get returned bad response code: $0", |
70 | 0 | RedisResponsePB_RedisStatusCode_Name(get_op->response().code())); |
71 | 0 | } |
72 | 0 | if (get_op->response().string_response() != value) { |
73 | 0 | return STATUS_FORMAT(RuntimeError, |
74 | 0 | "Redis get returned wrong value: $0 != $1", |
75 | 0 | get_op->response().string_response(), value); |
76 | 0 | } |
77 | 0 | return Status::OK(); |
78 | 0 | } |
79 | | |
80 | | CHECKED_STATUS RedisSet(std::shared_ptr<client::YBSession> session, |
81 | | const std::shared_ptr<client::YBTable> table, |
82 | | const string& key, |
83 | 0 | const string& value) { |
84 | 0 | auto set_op = std::make_shared<client::YBRedisWriteOp>(table); |
85 | 0 | RETURN_NOT_OK(redisserver::ParseSet(set_op.get(), |
86 | 0 | redisserver::RedisClientCommand({"set", key, value}))); |
87 | 0 | RETURN_NOT_OK(session->ApplyAndFlush(set_op)); |
88 | 0 | return Status::OK(); |
89 | 0 | } |
90 | | } // namespace helpers |
91 | | |
92 | | class YBBackupTest : public pgwrapper::PgCommandTestBase { |
93 | | protected: |
94 | 0 | YBBackupTest() : pgwrapper::PgCommandTestBase(false, false) {} |
95 | | |
96 | 0 | void SetUp() override { |
97 | 0 | pgwrapper::PgCommandTestBase::SetUp(); |
98 | 0 | ASSERT_OK(CreateClient()); |
99 | 0 | } |
100 | | |
101 | 0 | string GetTempDir(const string& subdir) { |
102 | 0 | return tmp_dir_ / subdir; |
103 | 0 | } |
104 | | |
105 | 0 | Status RunBackupCommand(const vector<string>& args) { |
106 | 0 | return tools::RunBackupCommand( |
107 | 0 | cluster_->pgsql_hostport(0), cluster_->GetMasterAddresses(), |
108 | 0 | cluster_->GetTabletServerHTTPAddresses(), *tmp_dir_, args); |
109 | 0 | } |
110 | | |
111 | | void RecreateDatabase(const string& db) { |
112 | | ASSERT_NO_FATALS(RunPsqlCommand("CREATE DATABASE temp_db", "CREATE DATABASE")); |
113 | | SetDbName("temp_db"); // Connecting to the second DB from the moment. |
114 | | // Validate that the DB restoration works even if the default 'yugabyte' db was recreated. |
115 | | ASSERT_NO_FATALS(RunPsqlCommand(string("DROP DATABASE ") + db, "DROP DATABASE")); |
116 | | ASSERT_NO_FATALS(RunPsqlCommand(string("CREATE DATABASE ") + db, "CREATE DATABASE")); |
117 | | SetDbName(db); // Connecting to the recreated 'yugabyte' DB from the moment. |
118 | | } |
119 | | |
120 | | void DoTestYEDISBackup(helpers::TableOp tableOp); |
121 | | void DoTestYSQLKeyspaceBackup(helpers::TableOp tableOp); |
122 | | void DoTestYSQLMultiSchemaKeyspaceBackup(helpers::TableOp tableOp); |
123 | | |
124 | | client::TableHandle table_; |
125 | | TmpDirProvider tmp_dir_; |
126 | | }; |
127 | | |
128 | 0 | TEST_F(YBBackupTest, YB_DISABLE_TEST_IN_SANITIZERS_OR_MAC(TestYCQLKeyspaceBackup)) { |
129 | 0 | client::kv_table_test::CreateTable( |
130 | 0 | client::Transactional::kFalse, CalcNumTablets(3), client_.get(), &table_); |
131 | 0 | const string& keyspace = table_.name().namespace_name(); |
132 | |
|
133 | 0 | const string backup_dir = GetTempDir("backup"); |
134 | |
|
135 | 0 | ASSERT_OK(RunBackupCommand( |
136 | 0 | {"--backup_location", backup_dir, "--keyspace", keyspace, "create"})); |
137 | 0 | ASSERT_OK(RunBackupCommand( |
138 | 0 | {"--backup_location", backup_dir, "--keyspace", "new_" + keyspace, "restore"})); |
139 | |
|
140 | 0 | LOG(INFO) << "Test finished: " << CURRENT_TEST_CASE_AND_TEST_NAME_STR(); |
141 | 0 | } |
142 | | |
143 | | // 1. Insert abc -> 123 |
144 | | // 2. Backup |
145 | | // 3. Insert abc -> 456 OR drop redis table |
146 | | // 4. Restore |
147 | | // 5. Validate abc -> 123 |
148 | 0 | void YBBackupTest::DoTestYEDISBackup(helpers::TableOp tableOp) { |
149 | 0 | ASSERT_TRUE(tableOp == helpers::TableOp::kKeepTable || tableOp == helpers::TableOp::kDropTable); |
150 | |
|
151 | 0 | auto session = client_->NewSession(); |
152 | | |
153 | | // Create keyspace and table. |
154 | 0 | const client::YBTableName table_name( |
155 | 0 | YQL_DATABASE_REDIS, common::kRedisKeyspaceName, common::kRedisTableName); |
156 | 0 | ASSERT_OK(client_->CreateNamespaceIfNotExists(common::kRedisKeyspaceName, |
157 | 0 | YQLDatabase::YQL_DATABASE_REDIS)); |
158 | 0 | std::unique_ptr<yb::client::YBTableCreator> table_creator(client_->NewTableCreator()); |
159 | 0 | ASSERT_OK(table_creator->table_name(table_name) |
160 | 0 | .table_type(yb::client::YBTableType::REDIS_TABLE_TYPE) |
161 | 0 | .Create()); |
162 | 0 | ASSERT_OK(table_.Open(table_name, client_.get())); |
163 | 0 | auto table = table_->shared_from_this(); |
164 | | |
165 | | // Insert abc -> 123. |
166 | 0 | ASSERT_OK(helpers::RedisSet(session, table, "abc", "123")); |
167 | | |
168 | | // Backup. |
169 | 0 | const string backup_dir = GetTempDir("backup"); |
170 | 0 | ASSERT_OK(RunBackupCommand( |
171 | 0 | {"--backup_location", backup_dir, |
172 | 0 | "--keyspace", common::kRedisKeyspaceName, |
173 | 0 | "--table", common::kRedisTableName, |
174 | 0 | "create"})); |
175 | |
|
176 | 0 | if (tableOp == helpers::TableOp::kKeepTable) { |
177 | | // Insert abc -> 456. |
178 | 0 | ASSERT_OK(helpers::RedisSet(session, table, "abc", "456")); |
179 | 0 | ASSERT_OK(helpers::RedisGet(session, table, "abc", "456")); |
180 | 0 | } else { |
181 | 0 | ASSERT_EQ(tableOp, helpers::TableOp::kDropTable); |
182 | | // Delete table. |
183 | 0 | ASSERT_OK(client_->DeleteTable(table_name)); |
184 | 0 | ASSERT_FALSE(ASSERT_RESULT(client_->TableExists(table_name))); |
185 | 0 | } |
186 | | |
187 | | // Restore. |
188 | 0 | ASSERT_OK(RunBackupCommand({"--backup_location", backup_dir, "restore"})); |
189 | |
|
190 | 0 | if (tableOp == helpers::TableOp::kDropTable) { |
191 | | // Refresh table variable to the one newly created by restore. |
192 | 0 | ASSERT_OK(table_.Open(table_name, client_.get())); |
193 | 0 | table = table_->shared_from_this(); |
194 | 0 | } |
195 | | |
196 | | // Validate abc -> 123. |
197 | 0 | ASSERT_TRUE(ASSERT_RESULT(client_->TableExists(table_name))); |
198 | 0 | ASSERT_OK(helpers::RedisGet(session, table, "abc", "123")); |
199 | 0 | } |
200 | | |
201 | | // Exercise the CatalogManager::ImportTableEntry first code path where namespace ids and table ids |
202 | | // match. |
203 | 0 | TEST_F(YBBackupTest, YB_DISABLE_TEST_IN_SANITIZERS_OR_MAC(TestYEDISBackup)) { |
204 | 0 | DoTestYEDISBackup(helpers::TableOp::kKeepTable); |
205 | 0 | } |
206 | | |
207 | | // Exercise the CatalogManager::ImportTableEntry second code path where, instead, table names match. |
208 | 0 | TEST_F(YBBackupTest, YB_DISABLE_TEST_IN_SANITIZERS_OR_MAC(TestYEDISBackupWithDropTable)) { |
209 | 0 | DoTestYEDISBackup(helpers::TableOp::kDropTable); |
210 | 0 | } |
211 | | |
212 | 0 | TEST_F(YBBackupTest, YB_DISABLE_TEST_IN_SANITIZERS_OR_MAC(TestYSQLBackupWithEnum)) { |
213 | 0 | ASSERT_NO_FATALS(CreateType("CREATE TYPE e_t as ENUM ('foo', 'bar', 'cab')")); |
214 | 0 | ASSERT_NO_FATALS(CreateTable("CREATE TABLE mytbl (k INT PRIMARY KEY, v e_t)")); |
215 | 0 | ASSERT_NO_FATALS(InsertOneRow("INSERT INTO mytbl (k, v) VALUES (100, 'foo')")); |
216 | 0 | ASSERT_NO_FATALS(InsertOneRow("INSERT INTO mytbl (k, v) VALUES (101, 'bar')")); |
217 | 0 | ASSERT_NO_FATALS(InsertOneRow("INSERT INTO mytbl (k, v) VALUES (102, 'cab')")); |
218 | |
|
219 | 0 | const string backup_dir = GetTempDir("backup"); |
220 | 0 | ASSERT_OK(RunBackupCommand( |
221 | 0 | {"--backup_location", backup_dir, "--keyspace", "ysql.yugabyte", "create"})); |
222 | 0 | ASSERT_NO_FATALS(InsertOneRow("INSERT INTO mytbl (k, v) VALUES (999, 'foo')")); |
223 | 0 | ASSERT_OK(RunBackupCommand( |
224 | 0 | {"--backup_location", backup_dir, "--keyspace", "ysql.yugabyte_new", "restore"})); |
225 | |
|
226 | 0 | SetDbName("yugabyte_new"); // Connecting to the second DB from the moment. |
227 | 0 | ASSERT_NO_FATALS(RunPsqlCommand( |
228 | 0 | "SELECT k, v FROM mytbl ORDER BY k", |
229 | 0 | R"#( |
230 | 0 | k | v |
231 | 0 | -----+----- |
232 | 0 | 100 | foo |
233 | 0 | 101 | bar |
234 | 0 | 102 | cab |
235 | 0 | (3 rows) |
236 | 0 | )#" |
237 | 0 | )); |
238 | 0 | LOG(INFO) << "Test finished: " << CURRENT_TEST_CASE_AND_TEST_NAME_STR(); |
239 | 0 | } |
240 | | |
241 | 0 | TEST_F(YBBackupTest, YB_DISABLE_TEST_IN_SANITIZERS_OR_MAC(TestYSQLPgBasedBackup)) { |
242 | 0 | ASSERT_NO_FATALS(CreateTable("CREATE TABLE mytbl (k INT PRIMARY KEY, v TEXT)")); |
243 | 0 | ASSERT_NO_FATALS(InsertOneRow("INSERT INTO mytbl (k, v) VALUES (100, 'abc')")); |
244 | |
|
245 | 0 | const string backup_dir = GetTempDir("backup"); |
246 | 0 | ASSERT_OK(RunBackupCommand( |
247 | 0 | {"--pg_based_backup", "--backup_location", backup_dir, "--keyspace", "ysql.yugabyte", |
248 | 0 | "create"})); |
249 | 0 | ASSERT_NO_FATALS(InsertOneRow("INSERT INTO mytbl (k, v) VALUES (999, 'foo')")); |
250 | 0 | ASSERT_OK(RunBackupCommand( |
251 | 0 | {"--backup_location", backup_dir, "--keyspace", "ysql.yugabyte_new", "restore"})); |
252 | |
|
253 | 0 | SetDbName("yugabyte_new"); // Connecting to the second DB from the moment. |
254 | 0 | ASSERT_NO_FATALS(RunPsqlCommand( |
255 | 0 | "SELECT k, v FROM mytbl ORDER BY k", |
256 | 0 | R"#( |
257 | 0 | k | v |
258 | 0 | -----+----- |
259 | 0 | 100 | abc |
260 | 0 | (1 row) |
261 | 0 | )#" |
262 | 0 | )); |
263 | 0 | LOG(INFO) << "Test finished: " << CURRENT_TEST_CASE_AND_TEST_NAME_STR(); |
264 | 0 | } |
265 | | |
266 | 0 | void YBBackupTest::DoTestYSQLKeyspaceBackup(helpers::TableOp tableOp) { |
267 | 0 | ASSERT_NO_FATALS(CreateTable("CREATE TABLE mytbl (k INT PRIMARY KEY, v TEXT)")); |
268 | 0 | ASSERT_NO_FATALS(InsertOneRow("INSERT INTO mytbl (k, v) VALUES (100, 'foo')")); |
269 | 0 | ASSERT_NO_FATALS(RunPsqlCommand( |
270 | 0 | "SELECT k, v FROM mytbl ORDER BY k", |
271 | 0 | R"#( |
272 | 0 | k | v |
273 | 0 | -----+----- |
274 | 0 | 100 | foo |
275 | 0 | (1 row) |
276 | 0 | )#" |
277 | 0 | )); |
278 | |
|
279 | 0 | const string backup_dir = GetTempDir("backup"); |
280 | | |
281 | | // There is no YCQL keyspace 'yugabyte'. |
282 | 0 | ASSERT_NOK(RunBackupCommand( |
283 | 0 | {"--backup_location", backup_dir, "--keyspace", "yugabyte", "create"})); |
284 | |
|
285 | 0 | ASSERT_OK(RunBackupCommand( |
286 | 0 | {"--backup_location", backup_dir, "--keyspace", "ysql.yugabyte", "create"})); |
287 | |
|
288 | 0 | ASSERT_NO_FATALS(InsertOneRow("INSERT INTO mytbl (k, v) VALUES (200, 'bar')")); |
289 | 0 | ASSERT_NO_FATALS(RunPsqlCommand( |
290 | 0 | "SELECT k, v FROM mytbl ORDER BY k", |
291 | 0 | R"#( |
292 | 0 | k | v |
293 | 0 | -----+----- |
294 | 0 | 100 | foo |
295 | 0 | 200 | bar |
296 | 0 | (2 rows) |
297 | 0 | )#" |
298 | 0 | )); |
299 | |
|
300 | 0 | if (tableOp == helpers::TableOp::kDropTable) { |
301 | | // Validate that the DB restoration works even if we have deleted tables with the same name. |
302 | 0 | ASSERT_NO_FATALS(RunPsqlCommand("DROP TABLE mytbl", "DROP TABLE")); |
303 | 0 | } else if (tableOp == helpers::TableOp::kDropDB) { |
304 | 0 | RecreateDatabase("yugabyte"); |
305 | 0 | } |
306 | | |
307 | | // Restore into the original "ysql.yugabyte" YSQL DB. |
308 | 0 | ASSERT_OK(RunBackupCommand({"--backup_location", backup_dir, "restore"})); |
309 | | |
310 | | // Check the table data. |
311 | 0 | ASSERT_NO_FATALS(RunPsqlCommand( |
312 | 0 | "SELECT k, v FROM mytbl ORDER BY k", |
313 | 0 | R"#( |
314 | 0 | k | v |
315 | 0 | -----+----- |
316 | 0 | 100 | foo |
317 | 0 | (1 row) |
318 | 0 | )#" |
319 | 0 | )); |
320 | 0 | } |
321 | | |
322 | 0 | TEST_F(YBBackupTest, YB_DISABLE_TEST_IN_SANITIZERS_OR_MAC(TestYSQLKeyspaceBackup)) { |
323 | 0 | DoTestYSQLKeyspaceBackup(helpers::TableOp::kKeepTable); |
324 | 0 | LOG(INFO) << "Test finished: " << CURRENT_TEST_CASE_AND_TEST_NAME_STR(); |
325 | 0 | } |
326 | | |
327 | 0 | TEST_F(YBBackupTest, YB_DISABLE_TEST_IN_SANITIZERS_OR_MAC(TestYSQLKeyspaceBackupWithDropTable)) { |
328 | 0 | DoTestYSQLKeyspaceBackup(helpers::TableOp::kDropTable); |
329 | 0 | LOG(INFO) << "Test finished: " << CURRENT_TEST_CASE_AND_TEST_NAME_STR(); |
330 | 0 | } |
331 | | |
332 | 0 | TEST_F(YBBackupTest, YB_DISABLE_TEST_IN_SANITIZERS_OR_MAC(TestYSQLBackupWithDropYugabyteDB)) { |
333 | 0 | DoTestYSQLKeyspaceBackup(helpers::TableOp::kDropDB); |
334 | 0 | LOG(INFO) << "Test finished: " << CURRENT_TEST_CASE_AND_TEST_NAME_STR(); |
335 | 0 | } |
336 | | |
337 | | void YBBackupTest::DoTestYSQLMultiSchemaKeyspaceBackup(helpers::TableOp tableOp) { |
338 | | ASSERT_NO_FATALS(CreateSchema("CREATE SCHEMA schema1")); |
339 | | ASSERT_NO_FATALS(CreateTable("CREATE TABLE schema1.mytbl (k INT PRIMARY KEY, v TEXT)")); |
340 | | ASSERT_NO_FATALS(CreateIndex("CREATE INDEX mytbl_idx ON schema1.mytbl (v)")); |
341 | | |
342 | | ASSERT_NO_FATALS(CreateSchema("CREATE SCHEMA schema2")); |
343 | | ASSERT_NO_FATALS(CreateTable("CREATE TABLE schema2.mytbl (h1 TEXT PRIMARY KEY, v1 INT)")); |
344 | | ASSERT_NO_FATALS(CreateIndex("CREATE INDEX mytbl_idx ON schema2.mytbl (v1)")); |
345 | | |
346 | | ASSERT_NO_FATALS(InsertOneRow("INSERT INTO schema1.mytbl (k, v) VALUES (100, 'foo')")); |
347 | | ASSERT_NO_FATALS(RunPsqlCommand( |
348 | | "SELECT k, v FROM schema1.mytbl ORDER BY k", |
349 | | R"#( |
350 | | k | v |
351 | | -----+----- |
352 | | 100 | foo |
353 | | (1 row) |
354 | | )#" |
355 | | )); |
356 | | |
357 | | ASSERT_NO_FATALS(InsertOneRow("INSERT INTO schema2.mytbl (h1, v1) VALUES ('text1', 222)")); |
358 | | ASSERT_NO_FATALS(RunPsqlCommand( |
359 | | "SELECT h1, v1 FROM schema2.mytbl ORDER BY h1", |
360 | | R"#( |
361 | | h1 | v1 |
362 | | -------+----- |
363 | | text1 | 222 |
364 | | (1 row) |
365 | | )#" |
366 | | )); |
367 | | |
368 | | const string backup_dir = GetTempDir("backup"); |
369 | | |
370 | | ASSERT_OK(RunBackupCommand( |
371 | | {"--backup_location", backup_dir, "--keyspace", "ysql.yugabyte", "create"})); |
372 | | |
373 | | ASSERT_NO_FATALS(InsertOneRow("INSERT INTO schema1.mytbl (k, v) VALUES (200, 'bar')")); |
374 | | ASSERT_NO_FATALS(RunPsqlCommand( |
375 | | "SELECT k, v FROM schema1.mytbl ORDER BY k", |
376 | | R"#( |
377 | | k | v |
378 | | -----+----- |
379 | | 100 | foo |
380 | | 200 | bar |
381 | | (2 rows) |
382 | | )#" |
383 | | )); |
384 | | |
385 | | ASSERT_NO_FATALS(InsertOneRow("INSERT INTO schema2.mytbl (h1, v1) VALUES ('text2', 333)")); |
386 | | ASSERT_NO_FATALS(RunPsqlCommand( |
387 | | "SELECT h1, v1 FROM schema2.mytbl ORDER BY h1", |
388 | | R"#( |
389 | | h1 | v1 |
390 | | -------+----- |
391 | | text1 | 222 |
392 | | text2 | 333 |
393 | | (2 rows) |
394 | | )#" |
395 | | )); |
396 | | |
397 | | if (tableOp == helpers::TableOp::kDropTable) { |
398 | | // Validate that the DB restoration works even if we have deleted tables with the same name. |
399 | | ASSERT_NO_FATALS(RunPsqlCommand("DROP TABLE schema1.mytbl", "DROP TABLE")); |
400 | | ASSERT_NO_FATALS(RunPsqlCommand("DROP TABLE schema2.mytbl", "DROP TABLE")); |
401 | | } else if (tableOp == helpers::TableOp::kDropDB) { |
402 | | RecreateDatabase("yugabyte"); |
403 | | } |
404 | | |
405 | | // Restore into the original "ysql.yugabyte" YSQL DB. |
406 | | ASSERT_OK(RunBackupCommand({"--backup_location", backup_dir, "restore"})); |
407 | | |
408 | | // Check the table data. |
409 | | ASSERT_NO_FATALS(RunPsqlCommand( |
410 | | "SELECT k, v FROM schema1.mytbl ORDER BY k", |
411 | | R"#( |
412 | | k | v |
413 | | -----+----- |
414 | | 100 | foo |
415 | | (1 row) |
416 | | )#" |
417 | | )); |
418 | | // Via schema1.mytbl_idx: |
419 | | ASSERT_NO_FATALS(RunPsqlCommand( |
420 | | "SELECT k, v FROM schema1.mytbl WHERE v='foo' OR v='bar'", |
421 | | R"#( |
422 | | k | v |
423 | | -----+----- |
424 | | 100 | foo |
425 | | (1 row) |
426 | | )#" |
427 | | )); |
428 | | |
429 | | ASSERT_NO_FATALS(RunPsqlCommand( |
430 | | "SELECT h1, v1 FROM schema2.mytbl ORDER BY h1", |
431 | | R"#( |
432 | | h1 | v1 |
433 | | -------+----- |
434 | | text1 | 222 |
435 | | (1 row) |
436 | | )#" |
437 | | )); |
438 | | // Via schema2.mytbl_idx: |
439 | | ASSERT_NO_FATALS(RunPsqlCommand( |
440 | | "SELECT h1, v1 FROM schema2.mytbl WHERE v1=222 OR v1=333", |
441 | | R"#( |
442 | | h1 | v1 |
443 | | -------+----- |
444 | | text1 | 222 |
445 | | (1 row) |
446 | | )#" |
447 | | )); |
448 | | |
449 | | LOG(INFO) << "Test finished: " << CURRENT_TEST_CASE_AND_TEST_NAME_STR(); |
450 | | } |
451 | | |
452 | 0 | TEST_F(YBBackupTest, YB_DISABLE_TEST_IN_SANITIZERS_OR_MAC(TestYSQLMultiSchemaKeyspaceBackup)) { |
453 | 0 | DoTestYSQLMultiSchemaKeyspaceBackup(helpers::TableOp::kKeepTable); |
454 | 0 | LOG(INFO) << "Test finished: " << CURRENT_TEST_CASE_AND_TEST_NAME_STR(); |
455 | 0 | } |
456 | | |
457 | | TEST_F(YBBackupTest, |
458 | 0 | YB_DISABLE_TEST_IN_SANITIZERS_OR_MAC(TestYSQLMultiSchemaKeyspaceBackupWithDropTable)) { |
459 | 0 | DoTestYSQLMultiSchemaKeyspaceBackup(helpers::TableOp::kDropTable); |
460 | 0 | LOG(INFO) << "Test finished: " << CURRENT_TEST_CASE_AND_TEST_NAME_STR(); |
461 | 0 | } |
462 | | |
463 | | TEST_F(YBBackupTest, |
464 | 0 | YB_DISABLE_TEST_IN_SANITIZERS_OR_MAC(TestYSQLMultiSchemaKeyspaceBackupWithDropDB)) { |
465 | 0 | DoTestYSQLMultiSchemaKeyspaceBackup(helpers::TableOp::kDropDB); |
466 | 0 | LOG(INFO) << "Test finished: " << CURRENT_TEST_CASE_AND_TEST_NAME_STR(); |
467 | 0 | } |
468 | | |
469 | | // Create two schemas. Create a table with the same name and columns in each of them. Restore onto a |
470 | | // cluster where the schema names swapped. Restore should succeed because the tables are not found |
471 | | // in the ids check phase but later found in the names check phase. |
472 | 0 | TEST_F(YBBackupTest, YB_DISABLE_TEST_IN_SANITIZERS_OR_MAC(TestYSQLSameIdDifferentSchemaName)) { |
473 | | // Initialize data: |
474 | | // - s1.mytbl: (1, 1) |
475 | | // - s2.mytbl: (2, 2) |
476 | 0 | auto schemas = {"s1", "s2"}; |
477 | 0 | for (const string& schema : schemas) { |
478 | 0 | ASSERT_NO_FATALS(CreateSchema(Format("CREATE SCHEMA $0", schema))); |
479 | 0 | ASSERT_NO_FATALS(CreateTable( |
480 | 0 | Format("CREATE TABLE $0.mytbl (k INT PRIMARY KEY, v INT)", schema))); |
481 | 0 | const string& substr = schema.substr(1, 1); |
482 | 0 | ASSERT_NO_FATALS(InsertOneRow( |
483 | 0 | Format("INSERT INTO $0.mytbl (k, v) VALUES ($1, $1)", schema, substr))); |
484 | 0 | ASSERT_NO_FATALS(RunPsqlCommand( |
485 | 0 | Format("SELECT k, v FROM $0.mytbl", schema), |
486 | 0 | Format(R"#( |
487 | 0 | k | v |
488 | 0 | ---+--- |
489 | 0 | $0 | $0 |
490 | 0 | (1 row) |
491 | 0 | )#", substr))); |
492 | 0 | } |
493 | | |
494 | | // Do backup. |
495 | 0 | const string backup_dir = GetTempDir("backup"); |
496 | 0 | ASSERT_OK(RunBackupCommand( |
497 | 0 | {"--backup_location", backup_dir, "--keyspace", "ysql.yugabyte", "create"})); |
498 | | |
499 | | // Add extra data to show that, later, restore actually happened. This is not the focus of the |
500 | | // test, but it helps us figure out whether backup/restore is to blame in the event of a test |
501 | | // failure. |
502 | 0 | for (const string& schema : schemas) { |
503 | 0 | ASSERT_NO_FATALS(InsertOneRow( |
504 | 0 | Format("INSERT INTO $0.mytbl (k, v) VALUES ($1, $1)", schema, 3))); |
505 | 0 | ASSERT_NO_FATALS(RunPsqlCommand( |
506 | 0 | Format("SELECT k, v FROM $0.mytbl ORDER BY k", schema), |
507 | 0 | Format(R"#( |
508 | 0 | k | v |
509 | 0 | ---+--- |
510 | 0 | $0 | $0 |
511 | 0 | 3 | 3 |
512 | 0 | (2 rows) |
513 | 0 | )#", schema.substr(1, 1)))); |
514 | 0 | } |
515 | | |
516 | | // Swap the schema names. |
517 | 0 | ASSERT_NO_FATALS(RunPsqlCommand( |
518 | 0 | Format("ALTER SCHEMA $0 RENAME TO $1", "s1", "stmp"), "ALTER SCHEMA")); |
519 | 0 | ASSERT_NO_FATALS(RunPsqlCommand( |
520 | 0 | Format("ALTER SCHEMA $0 RENAME TO $1", "s2", "s1"), "ALTER SCHEMA")); |
521 | 0 | ASSERT_NO_FATALS(RunPsqlCommand( |
522 | 0 | Format("ALTER SCHEMA $0 RENAME TO $1", "stmp", "s2"), "ALTER SCHEMA")); |
523 | | |
524 | | // Restore into the current "ysql.yugabyte" YSQL DB. Since we didn't drop anything, the ysql_dump |
525 | | // step should fail to create anything, behaving as a no op. This means that the schema name swap |
526 | | // will stay intact, as desired. |
527 | 0 | ASSERT_OK(RunBackupCommand({"--backup_location", backup_dir, "restore"})); |
528 | | |
529 | | // Check the table data. This is the main check of the test! Restore should make sure that schema |
530 | | // names match. |
531 | | // |
532 | | // Table s1.mytbl was renamed to s2.mytbl: let's call the table id "x". Snapshot's table id x |
533 | | // corresponds to s1.mytbl; active cluster's table id x corresponds to s2.mytbl. When importing |
534 | | // snapshot s1.mytbl, we first look up table with id x and find active s2.mytbl. However, after |
535 | | // checking s1 and s2 names mismatch, we disregard this attempt and move on to the second search, |
536 | | // which matches names rather than ids. Then, we restore s1.mytbl snapshot to live s1.mytbl: the |
537 | | // data on s1.mytbl will be (1, 1). |
538 | 0 | for (const string& schema : schemas) { |
539 | 0 | ASSERT_NO_FATALS(RunPsqlCommand( |
540 | 0 | Format("SELECT k, v FROM $0.mytbl", schema), |
541 | 0 | Format(R"#( |
542 | 0 | k | v |
543 | 0 | ---+--- |
544 | 0 | $0 | $0 |
545 | 0 | (1 row) |
546 | 0 | )#", schema.substr(1, 1)))); |
547 | 0 | } |
548 | |
|
549 | 0 | LOG(INFO) << "Test finished: " << CURRENT_TEST_CASE_AND_TEST_NAME_STR(); |
550 | 0 | } |
551 | | |
552 | | TEST_F(YBBackupTest, YB_DISABLE_TEST_IN_SANITIZERS_OR_MAC(TestYSQLRestoreBackupToNewKeyspace)) { |
553 | | // Test hash-table. |
554 | | ASSERT_NO_FATALS(CreateTable("CREATE TABLE hashtbl(k INT PRIMARY KEY, v TEXT)")); |
555 | | // Test single shard range-table. |
556 | | ASSERT_NO_FATALS(CreateTable("CREATE TABLE rangetbl(k INT, PRIMARY KEY(k ASC))")); |
557 | | // Test table containing serial column. |
558 | | ASSERT_NO_FATALS(CreateTable("CREATE TABLE serialtbl(k SERIAL PRIMARY KEY, v TEXT)")); |
559 | | |
560 | | ASSERT_NO_FATALS(CreateTable("CREATE TABLE vendors(v_code INT PRIMARY KEY, v_name TEXT)")); |
561 | | // Test Index. |
562 | | ASSERT_NO_FATALS(CreateIndex("CREATE UNIQUE INDEX ON vendors(v_name)")); |
563 | | // Test View. |
564 | | ASSERT_NO_FATALS(CreateView("CREATE VIEW vendors_view AS " |
565 | | "SELECT * FROM vendors WHERE v_name = 'foo'")); |
566 | | // Test stored procedure. |
567 | | ASSERT_NO_FATALS(CreateProcedure( |
568 | | "CREATE PROCEDURE proc(n INT) LANGUAGE PLPGSQL AS $$ DECLARE c INT := 0; BEGIN WHILE c < n " |
569 | | "LOOP c := c + 1; INSERT INTO vendors (v_code) VALUES(c + 10); END LOOP; END; $$")); |
570 | | |
571 | | ASSERT_NO_FATALS(CreateTable("CREATE TABLE items(i_code INT, i_name TEXT, " |
572 | | "price numeric(10,2), PRIMARY KEY(i_code, i_name))")); |
573 | | // Test Foreign Key for 1 column and for 2 columns. |
574 | | ASSERT_NO_FATALS(CreateTable("CREATE TABLE orders(o_no INT PRIMARY KEY, o_date date, " |
575 | | "v_code INT REFERENCES vendors, i_code INT, i_name TEXT, " |
576 | | "FOREIGN KEY(i_code, i_name) REFERENCES items(i_code, i_name))")); |
577 | | |
578 | | ASSERT_NO_FATALS(RunPsqlCommand( |
579 | | "SELECT schemaname, indexname FROM pg_indexes WHERE tablename = 'vendors'", |
580 | | R"#( |
581 | | schemaname | indexname |
582 | | ------------+-------------------- |
583 | | public | vendors_pkey |
584 | | public | vendors_v_name_idx |
585 | | (2 rows) |
586 | | )#" |
587 | | )); |
588 | | ASSERT_NO_FATALS(RunPsqlCommand( |
589 | | "\\d vendors_view", |
590 | | R"#( |
591 | | View "public.vendors_view" |
592 | | Column | Type | Collation | Nullable | Default |
593 | | --------+---------+-----------+----------+--------- |
594 | | v_code | integer | | | |
595 | | v_name | text | | | |
596 | | )#" |
597 | | )); |
598 | | ASSERT_NO_FATALS(RunPsqlCommand( |
599 | | "\\d orders", |
600 | | R"#( |
601 | | Table "public.orders" |
602 | | Column | Type | Collation | Nullable | Default |
603 | | --------+---------+-----------+----------+--------- |
604 | | o_no | integer | | not null | |
605 | | o_date | date | | | |
606 | | v_code | integer | | | |
607 | | i_code | integer | | | |
608 | | i_name | text | | | |
609 | | Indexes: |
610 | | "orders_pkey" PRIMARY KEY, lsm (o_no HASH) |
611 | | Foreign-key constraints: |
612 | | "orders_i_code_fkey" FOREIGN KEY (i_code, i_name) REFERENCES items(i_code, i_name) |
613 | | "orders_v_code_fkey" FOREIGN KEY (v_code) REFERENCES vendors(v_code) |
614 | | )#" |
615 | | )); |
616 | | |
617 | | ASSERT_NO_FATALS(InsertOneRow("INSERT INTO vendors (v_code, v_name) VALUES (100, 'foo')")); |
618 | | |
619 | | const string backup_dir = GetTempDir("backup"); |
620 | | |
621 | | ASSERT_OK(RunBackupCommand( |
622 | | {"--backup_location", backup_dir, "--keyspace", "ysql.yugabyte", "create"})); |
623 | | |
624 | | ASSERT_NO_FATALS(InsertOneRow("INSERT INTO vendors (v_code, v_name) VALUES (200, 'bar')")); |
625 | | |
626 | | // Restore into new "ysql.yugabyte2" YSQL DB. |
627 | | ASSERT_OK(RunBackupCommand( |
628 | | {"--backup_location", backup_dir, "--keyspace", "ysql.yugabyte2", "restore"})); |
629 | | |
630 | | ASSERT_NO_FATALS(RunPsqlCommand( |
631 | | "SELECT v_code, v_name FROM vendors ORDER BY v_code", |
632 | | R"#( |
633 | | v_code | v_name |
634 | | --------+-------- |
635 | | 100 | foo |
636 | | 200 | bar |
637 | | (2 rows) |
638 | | )#" |
639 | | )); |
640 | | |
641 | | SetDbName("yugabyte2"); // Connecting to the second DB from the moment. |
642 | | |
643 | | // Check the tables. |
644 | | ASSERT_NO_FATALS(RunPsqlCommand( |
645 | | "\\dt", |
646 | | R"#( |
647 | | List of relations |
648 | | Schema | Name | Type | Owner |
649 | | --------+-----------+-------+---------- |
650 | | public | hashtbl | table | yugabyte |
651 | | public | items | table | yugabyte |
652 | | public | orders | table | yugabyte |
653 | | public | rangetbl | table | yugabyte |
654 | | public | serialtbl | table | yugabyte |
655 | | public | vendors | table | yugabyte |
656 | | (6 rows) |
657 | | )#" |
658 | | )); |
659 | | ASSERT_NO_FATALS(RunPsqlCommand( |
660 | | "\\d rangetbl", |
661 | | R"#( |
662 | | Table "public.rangetbl" |
663 | | Column | Type | Collation | Nullable | Default |
664 | | --------+---------+-----------+----------+--------- |
665 | | k | integer | | not null | |
666 | | Indexes: |
667 | | "rangetbl_pkey" PRIMARY KEY, lsm (k ASC) |
668 | | )#" |
669 | | )); |
670 | | ASSERT_NO_FATALS(RunPsqlCommand( |
671 | | "\\d serialtbl", |
672 | | R"#( |
673 | | Table "public.serialtbl" |
674 | | Column | Type | Collation | Nullable | Default |
675 | | --------+---------+-----------+----------+-------------------------------------- |
676 | | k | integer | | not null | nextval('serialtbl_k_seq'::regclass) |
677 | | v | text | | | |
678 | | Indexes: |
679 | | "serialtbl_pkey" PRIMARY KEY, lsm (k HASH) |
680 | | )#" |
681 | | )); |
682 | | ASSERT_NO_FATALS(RunPsqlCommand( |
683 | | "\\d vendors", |
684 | | R"#( |
685 | | Table "public.vendors" |
686 | | Column | Type | Collation | Nullable | Default |
687 | | --------+---------+-----------+----------+--------- |
688 | | v_code | integer | | not null | |
689 | | v_name | text | | | |
690 | | Indexes: |
691 | | "vendors_pkey" PRIMARY KEY, lsm (v_code HASH) |
692 | | "vendors_v_name_idx" UNIQUE, lsm (v_name HASH) |
693 | | Referenced by: |
694 | | )#" |
695 | | " TABLE \"orders\" CONSTRAINT \"orders_v_code_fkey\" FOREIGN KEY (v_code) " |
696 | | "REFERENCES vendors(v_code)" |
697 | | )); |
698 | | // Check the table data. |
699 | | ASSERT_NO_FATALS(RunPsqlCommand( |
700 | | "SELECT v_code, v_name FROM vendors ORDER BY v_code", |
701 | | R"#( |
702 | | v_code | v_name |
703 | | --------+-------- |
704 | | 100 | foo |
705 | | (1 row) |
706 | | )#" |
707 | | )); |
708 | | // Check the index data. |
709 | | ASSERT_NO_FATALS(RunPsqlCommand( |
710 | | "SELECT schemaname, indexname FROM pg_indexes WHERE tablename = 'vendors'", |
711 | | R"#( |
712 | | schemaname | indexname |
713 | | ------------+-------------------- |
714 | | public | vendors_pkey |
715 | | public | vendors_v_name_idx |
716 | | (2 rows) |
717 | | )#" |
718 | | )); |
719 | | ASSERT_NO_FATALS(RunPsqlCommand( |
720 | | "EXPLAIN (COSTS OFF) SELECT v_name FROM vendors WHERE v_name = 'foo'", |
721 | | R"#( |
722 | | QUERY PLAN |
723 | | ----------------------------------------------------- |
724 | | Index Only Scan using vendors_v_name_idx on vendors |
725 | | Index Cond: (v_name = 'foo'::text) |
726 | | (2 rows) |
727 | | )#" |
728 | | )); |
729 | | ASSERT_NO_FATALS(RunPsqlCommand( |
730 | | "SELECT v_name FROM vendors WHERE v_name = 'foo'", |
731 | | R"#( |
732 | | v_name |
733 | | -------- |
734 | | foo |
735 | | (1 row) |
736 | | )#" |
737 | | )); |
738 | | ASSERT_NO_FATALS(RunPsqlCommand( |
739 | | "EXPLAIN (COSTS OFF) SELECT * FROM vendors WHERE v_name = 'foo'", |
740 | | R"#( |
741 | | QUERY PLAN |
742 | | ------------------------------------------------ |
743 | | Index Scan using vendors_v_name_idx on vendors |
744 | | Index Cond: (v_name = 'foo'::text) |
745 | | (2 rows) |
746 | | )#" |
747 | | )); |
748 | | ASSERT_NO_FATALS(RunPsqlCommand( |
749 | | "SELECT * FROM vendors WHERE v_name = 'foo'", |
750 | | R"#( |
751 | | v_code | v_name |
752 | | --------+-------- |
753 | | 100 | foo |
754 | | (1 row) |
755 | | )#" |
756 | | )); |
757 | | // Check the view. |
758 | | ASSERT_NO_FATALS(RunPsqlCommand( |
759 | | "\\d vendors_view", |
760 | | R"#( |
761 | | View "public.vendors_view" |
762 | | Column | Type | Collation | Nullable | Default |
763 | | --------+---------+-----------+----------+--------- |
764 | | v_code | integer | | | |
765 | | v_name | text | | | |
766 | | )#" |
767 | | )); |
768 | | ASSERT_NO_FATALS(RunPsqlCommand( |
769 | | "SELECT * FROM vendors_view", |
770 | | R"#( |
771 | | v_code | v_name |
772 | | --------+-------- |
773 | | 100 | foo |
774 | | (1 row) |
775 | | )#" |
776 | | )); |
777 | | // Check the foreign keys. |
778 | | ASSERT_NO_FATALS(RunPsqlCommand( |
779 | | "\\d orders", |
780 | | R"#( |
781 | | Table "public.orders" |
782 | | Column | Type | Collation | Nullable | Default |
783 | | --------+---------+-----------+----------+--------- |
784 | | o_no | integer | | not null | |
785 | | o_date | date | | | |
786 | | v_code | integer | | | |
787 | | i_code | integer | | | |
788 | | i_name | text | | | |
789 | | Indexes: |
790 | | "orders_pkey" PRIMARY KEY, lsm (o_no HASH) |
791 | | Foreign-key constraints: |
792 | | "orders_i_code_fkey" FOREIGN KEY (i_code, i_name) REFERENCES items(i_code, i_name) |
793 | | "orders_v_code_fkey" FOREIGN KEY (v_code) REFERENCES vendors(v_code) |
794 | | )#" |
795 | | )); |
796 | | // Check the stored procedure. |
797 | | ASSERT_NO_FATALS(Call("CALL proc(3)")); |
798 | | ASSERT_NO_FATALS(RunPsqlCommand( |
799 | | "SELECT v_code, v_name FROM vendors ORDER BY v_code", |
800 | | R"#( |
801 | | v_code | v_name |
802 | | --------+-------- |
803 | | 11 | |
804 | | 12 | |
805 | | 13 | |
806 | | 100 | foo |
807 | | (4 rows) |
808 | | )#" |
809 | | )); |
810 | | |
811 | | LOG(INFO) << "Test finished: " << CURRENT_TEST_CASE_AND_TEST_NAME_STR(); |
812 | | } |
813 | | |
814 | 0 | TEST_F(YBBackupTest, YB_DISABLE_TEST_IN_SANITIZERS_OR_MAC(TestYBBackupWrongUsage)) { |
815 | 0 | client::kv_table_test::CreateTable( |
816 | 0 | client::Transactional::kTrue, CalcNumTablets(3), client_.get(), &table_); |
817 | 0 | const string& keyspace = table_.name().namespace_name(); |
818 | 0 | const string& table = table_.name().table_name(); |
819 | 0 | const string backup_dir = GetTempDir("backup"); |
820 | | |
821 | | // No 'create' or 'restore' argument. |
822 | 0 | ASSERT_NOK(RunBackupCommand({})); |
823 | | |
824 | | // No '--keyspace' argument. |
825 | 0 | ASSERT_NOK(RunBackupCommand({"--backup_location", backup_dir, "create"})); |
826 | 0 | ASSERT_NOK(RunBackupCommand({"--backup_location", backup_dir, "--table", table, "create"})); |
827 | |
|
828 | 0 | ASSERT_OK(RunBackupCommand({"--backup_location", backup_dir, "--keyspace", keyspace, "create"})); |
829 | | |
830 | | // No '--keyspace' argument, but there is '--table'. |
831 | 0 | ASSERT_NOK(RunBackupCommand( |
832 | 0 | {"--backup_location", backup_dir, "--table", "new_" + table, "restore"})); |
833 | |
|
834 | 0 | ASSERT_OK(RunBackupCommand({"--backup_location", backup_dir, "restore"})); |
835 | |
|
836 | 0 | LOG(INFO) << "Test finished: " << CURRENT_TEST_CASE_AND_TEST_NAME_STR(); |
837 | 0 | } |
838 | | |
839 | 0 | TEST_F(YBBackupTest, YB_DISABLE_TEST_IN_SANITIZERS_OR_MAC(TestYCQLBackupWithDefinedPartitions)) { |
840 | 0 | const int kNumPartitions = 3; |
841 | |
|
842 | 0 | const client::YBTableName kTableName(YQL_DATABASE_CQL, "my_keyspace", "test-table"); |
843 | |
|
844 | 0 | ASSERT_OK(client_->CreateNamespaceIfNotExists(kTableName.namespace_name(), |
845 | 0 | kTableName.namespace_type())); |
846 | 0 | std::unique_ptr<client::YBTableCreator> table_creator(client_->NewTableCreator()); |
847 | 0 | client::YBSchema client_schema(client::YBSchemaFromSchema(yb::GetSimpleTestSchema())); |
848 | | |
849 | | // Allocate the partitions. |
850 | 0 | Partition partitions[kNumPartitions]; |
851 | 0 | const uint16_t max_interval = PartitionSchema::kMaxPartitionKey; |
852 | 0 | const string key1 = PartitionSchema::EncodeMultiColumnHashValue(max_interval / 10); |
853 | 0 | const string key2 = PartitionSchema::EncodeMultiColumnHashValue(max_interval * 3 / 4); |
854 | |
|
855 | 0 | partitions[0].set_partition_key_end(key1); |
856 | 0 | partitions[1].set_partition_key_start(key1); |
857 | 0 | partitions[1].set_partition_key_end(key2); |
858 | 0 | partitions[2].set_partition_key_start(key2); |
859 | | |
860 | | // create a table |
861 | 0 | ASSERT_OK(table_creator->table_name(kTableName) |
862 | 0 | .schema(&client_schema) |
863 | 0 | .num_tablets(kNumPartitions) |
864 | 0 | .add_partition(partitions[0]) |
865 | 0 | .add_partition(partitions[1]) |
866 | 0 | .add_partition(partitions[2]) |
867 | 0 | .Create()); |
868 | |
|
869 | 0 | const string& keyspace = kTableName.namespace_name(); |
870 | 0 | const string backup_dir = GetTempDir("backup"); |
871 | |
|
872 | 0 | ASSERT_OK(RunBackupCommand( |
873 | 0 | {"--backup_location", backup_dir, "--keyspace", keyspace, "create"})); |
874 | 0 | ASSERT_OK(RunBackupCommand( |
875 | 0 | {"--backup_location", backup_dir, "--keyspace", "new_" + keyspace, "restore"})); |
876 | |
|
877 | 0 | const client::YBTableName kNewTableName(YQL_DATABASE_CQL, "new_my_keyspace", "test-table"); |
878 | 0 | google::protobuf::RepeatedPtrField<yb::master::TabletLocationsPB> tablets; |
879 | 0 | ASSERT_OK(client_->GetTablets( |
880 | 0 | kNewTableName, |
881 | 0 | -1, |
882 | 0 | &tablets, |
883 | 0 | /* partition_list_version =*/ nullptr, |
884 | 0 | RequireTabletsRunning::kFalse)); |
885 | 0 | for (int i = 0 ; i < kNumPartitions; ++i) { |
886 | 0 | Partition p; |
887 | 0 | Partition::FromPB(tablets[i].partition(), &p); |
888 | 0 | ASSERT_TRUE(partitions[i].BoundsEqualToPartition(p)); |
889 | 0 | } |
890 | 0 | } |
891 | | |
892 | | // Test backup/restore on table with UNIQUE constraint where the unique constraint is originally |
893 | | // range partitioned to multiple tablets. When creating the constraint, split to 3 tablets at custom |
894 | | // split points. When restoring, ysql_dump is not able to express the splits, so it will create the |
895 | | // constraint as 1 hash tablet. Restore should restore the unique constraint index as 3 tablets |
896 | | // since the tablet snapshot files are already split into 3 tablets. |
897 | | // |
898 | | // TODO(yguan): after the SPLIT AT clause is fully supported by ysql_dump this test needs to |
899 | | // be revisited as the table may no longer need re-partitioning. |
900 | | // Therefore, to exercise CatalogManager::RepartitionTable this test may need |
901 | | // to be updated similar to TestYSQLManualTabletSplit. |
902 | 0 | TEST_F(YBBackupTest, YB_DISABLE_TEST_IN_SANITIZERS_OR_MAC(TestYSQLRangeSplitConstraint)) { |
903 | 0 | const string table_name = "mytbl"; |
904 | 0 | const string index_name = "myidx"; |
905 | | |
906 | | // Create table with unique constraint where the unique constraint is custom range partitioned. |
907 | 0 | ASSERT_NO_FATALS(CreateTable( |
908 | 0 | Format("CREATE TABLE $0 (k SERIAL PRIMARY KEY, v TEXT)", table_name))); |
909 | 0 | ASSERT_NO_FATALS(CreateIndex( |
910 | 0 | Format("CREATE UNIQUE INDEX $0 ON $1 (v ASC) SPLIT AT VALUES (('foo'), ('qux'))", |
911 | 0 | index_name, table_name))); |
912 | | |
913 | | // Commenting out the ALTER .. ADD UNIQUE constraint case as this case is not supported. |
914 | | // Vanilla Postgres disallows adding indexes with non-default (DESC) sort option as constraints. |
915 | | // In YB we have added HASH and changed default (for first column) from ASC to HASH. |
916 | | // |
917 | | // See #11583 for details -- we should revisit this test after that is fixed. |
918 | | /* |
919 | | ASSERT_NO_FATALS(RunPsqlCommand( |
920 | | Format("ALTER TABLE $0 ADD UNIQUE USING INDEX $1", table_name, index_name), |
921 | | "ALTER TABLE")); |
922 | | */ |
923 | | |
924 | | // Write data in each partition of the index. |
925 | 0 | ASSERT_NO_FATALS(InsertRows( |
926 | 0 | Format("INSERT INTO $0 (v) VALUES ('tar'), ('bar'), ('jar')", table_name), 3)); |
927 | 0 | ASSERT_NO_FATALS(RunPsqlCommand( |
928 | 0 | Format("SELECT * FROM $0 ORDER BY v", table_name), |
929 | 0 | R"#( |
930 | 0 | k | v |
931 | 0 | ---+----- |
932 | 0 | 2 | bar |
933 | 0 | 3 | jar |
934 | 0 | 1 | tar |
935 | 0 | (3 rows) |
936 | 0 | )#" |
937 | 0 | )); |
938 | | |
939 | | // Backup. |
940 | 0 | const string backup_dir = GetTempDir("backup"); |
941 | 0 | ASSERT_OK(RunBackupCommand( |
942 | 0 | {"--backup_location", backup_dir, "--keyspace", "ysql.yugabyte", "create"})); |
943 | | |
944 | | // Drop the table (and index) so that, on restore, running the ysql_dump file recreates the table |
945 | | // (and index). |
946 | 0 | ASSERT_NO_FATALS(RunPsqlCommand(Format("DROP TABLE $0", table_name), "DROP TABLE")); |
947 | | |
948 | | // Restore should notice that the index it creates from ysql_dump file (1 tablet) differs from |
949 | | // the external snapshot (3 tablets), so it should adjust to match the snapshot (3 tablets). |
950 | 0 | ASSERT_OK(RunBackupCommand({"--backup_location", backup_dir, "restore"})); |
951 | | |
952 | | // Verify data. |
953 | 0 | ASSERT_NO_FATALS(RunPsqlCommand( |
954 | 0 | Format("SELECT * FROM $0 ORDER BY v", table_name), |
955 | 0 | R"#( |
956 | 0 | k | v |
957 | 0 | ---+----- |
958 | 0 | 2 | bar |
959 | 0 | 3 | jar |
960 | 0 | 1 | tar |
961 | 0 | (3 rows) |
962 | 0 | )#" |
963 | 0 | )); |
964 | |
|
965 | 0 | LOG(INFO) << "Test finished: " << CURRENT_TEST_CASE_AND_TEST_NAME_STR(); |
966 | 0 | } |
967 | | |
968 | | class YBBackupTestNumTablets : public YBBackupTest { |
969 | | public: |
970 | 0 | void UpdateMiniClusterOptions(ExternalMiniClusterOptions* options) override { |
971 | 0 | YBBackupTest::UpdateMiniClusterOptions(options); |
972 | | |
973 | | // For convenience, rather than create a subclass for tablet splitting tests, add tablet split |
974 | | // flags here since they shouldn't really affect non-tablet splitting tests. |
975 | 0 | options->extra_master_flags.push_back("--enable_automatic_tablet_splitting=false"); |
976 | 0 | options->extra_tserver_flags.push_back("--db_block_size_bytes=1024"); |
977 | 0 | options->extra_tserver_flags.push_back("--ycql_num_tablets=3"); |
978 | 0 | options->extra_tserver_flags.push_back("--ysql_num_tablets=3"); |
979 | 0 | } |
980 | | |
981 | | protected: |
982 | 0 | Result<string> GetTableId(const string& table_name, const string& log_prefix) { |
983 | 0 | LOG(INFO) << log_prefix << ": get table"; |
984 | 0 | vector<client::YBTableName> tables = VERIFY_RESULT(client_->ListTables(table_name)); |
985 | 0 | if (tables.size() != 1) { |
986 | 0 | return STATUS_FORMAT(InternalError, "Expected 1 table: got $0", tables.size()); |
987 | 0 | } |
988 | 0 | return tables.front().table_id(); |
989 | 0 | } |
990 | | |
991 | | Result<google::protobuf::RepeatedPtrField<yb::master::TabletLocationsPB>> GetTablets( |
992 | 0 | const string& table_name, const string& log_prefix) { |
993 | 0 | auto table_id = VERIFY_RESULT(GetTableId(table_name, log_prefix)); |
994 | |
|
995 | 0 | LOG(INFO) << log_prefix << ": get tablets"; |
996 | 0 | google::protobuf::RepeatedPtrField<yb::master::TabletLocationsPB> tablets; |
997 | 0 | RETURN_NOT_OK(client_->GetTabletsFromTableId(table_id, -1, &tablets)); |
998 | 0 | return tablets; |
999 | 0 | } |
1000 | | |
1001 | | Result<bool> CheckPartitions( |
1002 | | const google::protobuf::RepeatedPtrField<yb::master::TabletLocationsPB>& tablets, |
1003 | 0 | const vector<string>& expected_splits) { |
1004 | 0 | SCHECK_EQ( |
1005 | 0 | implicit_cast<size_t>(tablets.size()), expected_splits.size() + 1, InvalidArgument, ""); |
1006 | |
|
1007 | 0 | static const string empty; |
1008 | 0 | for (int i = 0; i < tablets.size(); i++) { |
1009 | 0 | const string& expected_start = (i == 0 ? empty : expected_splits[i-1]); |
1010 | 0 | const string& expected_end = (i == tablets.size() - 1 ? empty : expected_splits[i]); |
1011 | |
|
1012 | 0 | if (tablets[i].partition().partition_key_start() != expected_start) { |
1013 | 0 | LOG(WARNING) << "actual partition start " |
1014 | 0 | << b2a_hex(tablets[i].partition().partition_key_start()) |
1015 | 0 | << " not equal to expected start " |
1016 | 0 | << b2a_hex(expected_start); |
1017 | 0 | return false; |
1018 | 0 | } |
1019 | 0 | if (tablets[i].partition().partition_key_end() != expected_end) { |
1020 | 0 | LOG(WARNING) << "actual partition end " |
1021 | 0 | << b2a_hex(tablets[i].partition().partition_key_end()) |
1022 | 0 | << " not equal to expected end " |
1023 | 0 | << b2a_hex(expected_end); |
1024 | 0 | return false; |
1025 | 0 | } |
1026 | 0 | } |
1027 | 0 | return true; |
1028 | 0 | } |
1029 | | }; |
1030 | | |
1031 | | // Test backup/restore on table with UNIQUE constraint when default number of tablets differs. When |
1032 | | // creating the table, the default is 3; when restoring, the default is 2. Restore should restore |
1033 | | // the unique constraint index as 3 tablets since the tablet snapshot files are already split into 3 |
1034 | | // tablets. |
1035 | | // |
1036 | | // For debugging, run ./yb_build.sh with extra flags: |
1037 | | // - --extra-daemon-flags "--vmodule=client=1,table_creator=1" |
1038 | | // - --test-args "--verbose_yb_backup" |
1039 | | TEST_F_EX(YBBackupTest, |
1040 | | YB_DISABLE_TEST_IN_SANITIZERS_OR_MAC(TestYSQLChangeDefaultNumTablets), |
1041 | 0 | YBBackupTestNumTablets) { |
1042 | 0 | const string table_name = "mytbl"; |
1043 | 0 | const string index_name = table_name + "_v_key"; |
1044 | |
|
1045 | 0 | ASSERT_NO_FATALS(CreateTable(Format( |
1046 | 0 | "CREATE TABLE $0 (k INT PRIMARY KEY, v TEXT, UNIQUE (v))", table_name))); |
1047 | |
|
1048 | 0 | auto tablets = ASSERT_RESULT(GetTablets(index_name, "pre-backup")); |
1049 | 0 | ASSERT_EQ(tablets.size(), 3); |
1050 | |
|
1051 | 0 | const string backup_dir = GetTempDir("backup"); |
1052 | 0 | ASSERT_OK(RunBackupCommand( |
1053 | 0 | {"--backup_location", backup_dir, "--keyspace", "ysql.yugabyte", "create"})); |
1054 | | |
1055 | | // Drop the table (and index) so that, on restore, running the ysql_dump file recreates the table |
1056 | | // (and index). |
1057 | 0 | ASSERT_NO_FATALS(RunPsqlCommand(Format("DROP TABLE $0", table_name), "DROP TABLE")); |
1058 | | |
1059 | | // When restore runs the CREATE TABLE, make it run in an environment where the default number of |
1060 | | // tablets is different. Namely, run it with new default 2 (previously 3). This won't affect the |
1061 | | // table since the table is generated with SPLIT clause specifying 3, but it will change the way |
1062 | | // the unique index is created because the unique index has no corresponding grammar to specify |
1063 | | // number of splits in ysql_dump file. |
1064 | 0 | for (auto ts : cluster_->tserver_daemons()) { |
1065 | 0 | ts->Shutdown(); |
1066 | 0 | ts->mutable_flags()->push_back("--ysql_num_tablets=2"); |
1067 | 0 | ASSERT_OK(ts->Restart()); |
1068 | 0 | } |
1069 | | |
1070 | | // Check that --ysql_num_tablets=2 is working as intended by |
1071 | | // 1. running the CREATE TABLE that is expected to be found in the ysql_dump file and |
1072 | | // 2. finding 2 index tablets |
1073 | 0 | ASSERT_NO_FATALS(CreateTable(Format( |
1074 | 0 | "CREATE TABLE $0 (k INT PRIMARY KEY, v TEXT, UNIQUE (v))", table_name))); |
1075 | 0 | tablets = ASSERT_RESULT(GetTablets(index_name, "pre-restore")); |
1076 | 0 | ASSERT_EQ(tablets.size(), 2); |
1077 | 0 | ASSERT_NO_FATALS(RunPsqlCommand(Format("DROP TABLE $0", table_name), "DROP TABLE")); |
1078 | | |
1079 | | // Restore should notice that the index it creates from ysql_dump file (2 tablets) differs from |
1080 | | // the external snapshot (3 tablets), so it should adjust to match the snapshot (3 tablets). |
1081 | 0 | ASSERT_OK(RunBackupCommand({"--backup_location", backup_dir, "restore"})); |
1082 | |
|
1083 | 0 | tablets = ASSERT_RESULT(GetTablets(index_name, "post-restore")); |
1084 | 0 | ASSERT_EQ(tablets.size(), 3); |
1085 | |
|
1086 | 0 | LOG(INFO) << "Test finished: " << CURRENT_TEST_CASE_AND_TEST_NAME_STR(); |
1087 | 0 | } |
1088 | | |
1089 | | // Test backup/restore when a hash-partitioned table undergoes manual tablet splitting. Most |
1090 | | // often, if tablets are split after creation, the partition boundaries will not be evenly spaced. |
1091 | | // This then differs from the boundaries of a hash table that is pre-split with the same number of |
1092 | | // tablets. Restoring snapshots to a table with differing partition boundaries should be detected |
1093 | | // and handled by repartitioning the table, even if the number of partitions are equal. This test |
1094 | | // exercises that: |
1095 | | // 1. start with 3 pre-split tablets |
1096 | | // 2. split one of them to make 4 tablets |
1097 | | // 3. backup |
1098 | | // 4. drop table |
1099 | | // 5. restore, which will initially create 4 pre-split tablets then realize the partition boundaries |
1100 | | // differ |
1101 | | TEST_F_EX(YBBackupTest, |
1102 | | YB_DISABLE_TEST_IN_SANITIZERS_OR_MAC(TestYSQLManualTabletSplit), |
1103 | 0 | YBBackupTestNumTablets) { |
1104 | 0 | const string table_name = "mytbl"; |
1105 | | |
1106 | | // Create table. |
1107 | 0 | ASSERT_NO_FATALS(CreateTable(Format("CREATE TABLE $0 (k INT PRIMARY KEY)", table_name))); |
1108 | | |
1109 | | // Insert rows that hash to each possible partition range for both manual split and even split. |
1110 | | // |
1111 | | // part range | k | hash | manual split part num | even split part num | interesting |
1112 | | // -0x3fff | 1 | 0x1210 | 1 | 1 | N |
1113 | | // 0x3fff-0x5555 | 6 | 0x4e58 | 1 | 2 | Y |
1114 | | // 0x5555-0x7ffe | 9 | 0x5d60 | 2 | 2 | N |
1115 | | // 0x7ffe-0x9c76 | 23 | 0x986c | 2 | 3 | Y |
1116 | | // 0x9c76-0xaaaa | 4 | 0x9eaf | 3 | 3 | N |
1117 | | // 0xaaaa-0xbffd | 27 | 0xbd51 | 4 | 3 | Y |
1118 | | // 0xbffd- | 2 | 0xc0c4 | 4 | 4 | N |
1119 | | // |
1120 | | // Split ranges are further discused in comments below. |
1121 | 0 | ASSERT_NO_FATALS(InsertRows( |
1122 | 0 | Format("INSERT INTO $0 VALUES (generate_series(1, 100))", table_name), 100)); |
1123 | 0 | string select_query = Format("SELECT k, to_hex(yb_hash_code(k)) AS hash FROM $0" |
1124 | 0 | " WHERE k IN (1, 2, 4, 6, 9, 23, 27) ORDER BY hash", |
1125 | 0 | table_name); |
1126 | 0 | string select_output = R"#( |
1127 | 0 | k | hash |
1128 | 0 | ----+------ |
1129 | 0 | 1 | 1210 |
1130 | 0 | 6 | 4e58 |
1131 | 0 | 9 | 5d60 |
1132 | 0 | 23 | 986c |
1133 | 0 | 4 | 9eaf |
1134 | 0 | 27 | bd51 |
1135 | 0 | 2 | c0c4 |
1136 | 0 | (7 rows) |
1137 | 0 | )#"; |
1138 | 0 | ASSERT_NO_FATALS(RunPsqlCommand(select_query, select_output)); |
1139 | | |
1140 | | // It has three tablets because of --ysql_num_tablets=3. |
1141 | 0 | auto tablets = ASSERT_RESULT(GetTablets(table_name, "pre-split")); |
1142 | 0 | for (const auto& tablet : tablets) { |
1143 | 0 | if (VLOG_IS_ON(1)) { |
1144 | 0 | VLOG(1) << "tablet location:\n" << tablet.DebugString(); |
1145 | 0 | } else { |
1146 | 0 | LOG(INFO) << "tablet_id: " << tablet.tablet_id() |
1147 | 0 | << ", partition: " << tablet.partition().ShortDebugString(); |
1148 | 0 | } |
1149 | 0 | } |
1150 | 0 | ASSERT_EQ(tablets.size(), 3); |
1151 | 0 | ASSERT_TRUE(ASSERT_RESULT(CheckPartitions(tablets, {"\x55\x55", "\xaa\xaa"}))); |
1152 | | |
1153 | | // Choose the middle tablet among |
1154 | | // - -0x5555 |
1155 | | // - 0x5555-0xaaaa |
1156 | | // - 0xaaaa- |
1157 | 0 | constexpr int middle_index = 1; |
1158 | 0 | ASSERT_EQ(tablets[middle_index].partition().partition_key_start(), "\x55\x55"); |
1159 | 0 | string tablet_id = tablets[middle_index].tablet_id(); |
1160 | | |
1161 | | // Flush table because it is necessary for manual tablet split. |
1162 | 0 | auto table_id = ASSERT_RESULT(GetTableId(table_name, "pre-split")); |
1163 | 0 | ASSERT_OK(client_->FlushTables({table_id}, false, 30, false)); |
1164 | | |
1165 | | // Split it. |
1166 | 0 | master::SplitTabletRequestPB req; |
1167 | 0 | req.set_tablet_id(tablet_id); |
1168 | 0 | master::SplitTabletResponsePB resp; |
1169 | 0 | rpc::RpcController rpc; |
1170 | 0 | rpc.set_timeout(30s * kTimeMultiplier); |
1171 | 0 | ASSERT_OK(cluster_->GetMasterProxy<master::MasterAdminProxy>().SplitTablet(req, &resp, &rpc)); |
1172 | | |
1173 | | // Wait for split to complete. |
1174 | 0 | constexpr int num_tablets = 4; |
1175 | 0 | ASSERT_OK(WaitFor( |
1176 | 0 | [&]() -> Result<bool> { |
1177 | 0 | auto res = VERIFY_RESULT(GetTablets(table_name, "wait-split")); |
1178 | 0 | return res.size() == num_tablets; |
1179 | 0 | }, 20s * kTimeMultiplier, Format("Waiting for tablet count: $0", num_tablets))); |
1180 | | |
1181 | | // Verify that it has these four tablets: |
1182 | | // - -0x5555 |
1183 | | // - 0x5555-0x9c76 |
1184 | | // - 0x9c76-0xaaaa |
1185 | | // - 0xaaaa- |
1186 | | // 0x9c76 just happens to be what tablet splitting chooses. Tablet splitting should choose the |
1187 | | // split point based on the existing data. Don't verify that it chose the right split point: that |
1188 | | // is out of scope of this test. Just trust what it chose. |
1189 | 0 | tablets = ASSERT_RESULT(GetTablets(table_name, "post-split")); |
1190 | 0 | for (const auto& tablet : tablets) { |
1191 | 0 | if (VLOG_IS_ON(1)) { |
1192 | 0 | VLOG(1) << "tablet location:\n" << tablet.DebugString(); |
1193 | 0 | } else { |
1194 | 0 | LOG(INFO) << "tablet_id: " << tablet.tablet_id() |
1195 | 0 | << ", split_depth: " << tablet.split_depth() |
1196 | 0 | << ", partition: " << tablet.partition().ShortDebugString(); |
1197 | 0 | } |
1198 | 0 | } |
1199 | 0 | ASSERT_EQ(tablets.size(), num_tablets); |
1200 | 0 | ASSERT_TRUE(ASSERT_RESULT(CheckPartitions(tablets, {"\x55\x55", "\x9c\x76", "\xaa\xaa"}))); |
1201 | |
|
1202 | 0 | const string backup_dir = GetTempDir("backup"); |
1203 | 0 | ASSERT_OK(RunBackupCommand( |
1204 | 0 | {"--backup_location", backup_dir, "--keyspace", "ysql.yugabyte", "create"})); |
1205 | | |
1206 | | // Drop the table so that, on restore, running the ysql_dump file recreates the table. ysql_dump |
1207 | | // should specify SPLIT INTO 4 TABLETS because the table in snapshot has 4 tablets. |
1208 | 0 | ASSERT_NO_FATALS(RunPsqlCommand(Format("DROP TABLE $0", table_name), "DROP TABLE")); |
1209 | | |
1210 | | // Before performing restore, demonstrate that the table that would be created by the ysql_dump |
1211 | | // file will have the following even splits: |
1212 | | // - -0x3fff |
1213 | | // - 0x3fff-0x7ffe |
1214 | | // - 0x7ffe-0xbffd |
1215 | | // - 0xbffd- |
1216 | | // Note: If this test starts failing because of this, the default splits probably changed to |
1217 | | // something more even like -0x4000, 0x4000-0x8000, and so forth. Simply adjust the test |
1218 | | // expectation here. |
1219 | 0 | ASSERT_NO_FATALS(CreateTable( |
1220 | 0 | Format("CREATE TABLE $0 (k INT PRIMARY KEY) SPLIT INTO 4 TABLETS", table_name))); |
1221 | 0 | tablets = ASSERT_RESULT(GetTablets(table_name, "mock-restore")); |
1222 | 0 | ASSERT_EQ(tablets.size(), 4); |
1223 | 0 | ASSERT_TRUE(ASSERT_RESULT(CheckPartitions(tablets, {"\x3f\xff", "\x7f\xfe", "\xbf\xfd"}))); |
1224 | 0 | ASSERT_NO_FATALS(RunPsqlCommand(Format("DROP TABLE $0", table_name), "DROP TABLE")); |
1225 | | |
1226 | | // Restore should notice that the table it creates from ysql_dump file has different partition |
1227 | | // boundaries from the one in the external snapshot EVEN THOUGH the number of partitions is four |
1228 | | // in both, so it should recreate partitions to match the splits in the snapshot. |
1229 | 0 | ASSERT_OK(RunBackupCommand({"--backup_location", backup_dir, "restore"})); |
1230 | | |
1231 | | // Validate. |
1232 | 0 | tablets = ASSERT_RESULT(GetTablets(table_name, "post-restore")); |
1233 | 0 | ASSERT_EQ(tablets.size(), 4); |
1234 | 0 | ASSERT_TRUE(ASSERT_RESULT(CheckPartitions(tablets, {"\x55\x55", "\x9c\x76", "\xaa\xaa"}))); |
1235 | 0 | ASSERT_NO_FATALS(RunPsqlCommand(select_query, select_output)); |
1236 | |
|
1237 | 0 | LOG(INFO) << "Test finished: " << CURRENT_TEST_CASE_AND_TEST_NAME_STR(); |
1238 | 0 | } |
1239 | | |
1240 | | class YBFailSnapshotTest: public YBBackupTest { |
1241 | 0 | void SetUp() override { |
1242 | 0 | YBBackupTest::SetUp(); |
1243 | 0 | } |
1244 | | |
1245 | 0 | void UpdateMiniClusterOptions(ExternalMiniClusterOptions* options) override { |
1246 | 0 | pgwrapper::PgCommandTestBase::UpdateMiniClusterOptions(options); |
1247 | 0 | options->extra_master_flags.push_back("--TEST_mark_snasphot_as_failed=true"); |
1248 | 0 | } |
1249 | | }; |
1250 | | |
1251 | | TEST_F_EX(YBBackupTest, |
1252 | | YB_DISABLE_TEST_IN_SANITIZERS_OR_MAC(TestFailBackupRestore), |
1253 | 0 | YBFailSnapshotTest) { |
1254 | 0 | client::kv_table_test::CreateTable( |
1255 | 0 | client::Transactional::kFalse, CalcNumTablets(3), client_.get(), &table_); |
1256 | 0 | const string& keyspace = table_.name().namespace_name(); |
1257 | 0 | const string backup_dir = GetTempDir("backup"); |
1258 | |
|
1259 | 0 | ASSERT_OK(RunBackupCommand( |
1260 | 0 | {"--backup_location", backup_dir, "--keyspace", keyspace, "create"})); |
1261 | 0 | Status s = RunBackupCommand( |
1262 | 0 | {"--backup_location", backup_dir, "--keyspace", "new_" + keyspace, "restore"}); |
1263 | 0 | ASSERT_NOK(s); |
1264 | 0 | ASSERT_STR_CONTAINS(s.message().ToBuffer(), ", restoring failed!"); |
1265 | |
|
1266 | 0 | LOG(INFO) << "Test finished: " << CURRENT_TEST_CASE_AND_TEST_NAME_STR(); |
1267 | 0 | } |
1268 | | |
1269 | 0 | TEST_F(YBBackupTest, YB_DISABLE_TEST_IN_SANITIZERS_OR_MAC(TestYCQLKeyspaceBackupWithLB)) { |
1270 | | // Create table with a lot of tablets. |
1271 | 0 | client::kv_table_test::CreateTable( |
1272 | 0 | client::Transactional::kFalse, CalcNumTablets(20), client_.get(), &table_); |
1273 | 0 | const string& keyspace = table_.name().namespace_name(); |
1274 | |
|
1275 | 0 | const string backup_dir = GetTempDir("backup"); |
1276 | |
|
1277 | 0 | ASSERT_OK(RunBackupCommand( |
1278 | 0 | {"--backup_location", backup_dir, "--keyspace", keyspace, "create"})); |
1279 | | |
1280 | | // Add in a new tserver to trigger the load balancer. |
1281 | 0 | ASSERT_OK(cluster_->AddTabletServer()); |
1282 | | |
1283 | | // Start running the restore while the load balancer is balancing the load. |
1284 | | // Use the --TEST_sleep_during_download_dir param to inject a sleep before the rsync calls. |
1285 | 0 | ASSERT_OK(RunBackupCommand( |
1286 | 0 | {"--backup_location", backup_dir, "--keyspace", "new_" + keyspace, |
1287 | 0 | "--TEST_sleep_during_download_dir", "restore"})); |
1288 | |
|
1289 | 0 | LOG(INFO) << "Test finished: " << CURRENT_TEST_CASE_AND_TEST_NAME_STR(); |
1290 | 0 | } |
1291 | | |
1292 | | } // namespace tools |
1293 | | } // namespace yb |