YugabyteDB (2.13.0.0-b42, bfc6a6643e7399ac8a0e81d06a3ee6d6571b33ab)

Coverage Report

Created: 2022-03-09 17:30

/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