1 // Copyright 2017 Google Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // 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
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "google/cloud/bigtable/testing/table_integration_test.h"
16 #include "google/cloud/internal/getenv.h"
17 #include "google/cloud/testing_util/assert_ok.h"
18 #include "absl/memory/memory.h"
19 #include <google/protobuf/text_format.h>
20 #include <algorithm>
21 #include <cctype>
22
23 namespace google {
24 namespace cloud {
25 namespace bigtable {
26 namespace testing {
27
28 std::string TableTestEnvironment::project_id_;
29 std::string TableTestEnvironment::instance_id_;
30 std::string TableTestEnvironment::zone_a_;
31 std::string TableTestEnvironment::zone_b_;
32 google::cloud::internal::DefaultPRNG TableTestEnvironment::generator_;
33 std::string TableTestEnvironment::table_id_;
34 bool TableTestEnvironment::using_cloud_bigtable_emulator_;
35
TableTestEnvironment()36 TableTestEnvironment::TableTestEnvironment() {
37 project_id_ =
38 google::cloud::internal::GetEnv("GOOGLE_CLOUD_PROJECT").value_or("");
39 instance_id_ = google::cloud::internal::GetEnv(
40 "GOOGLE_CLOUD_CPP_BIGTABLE_TEST_INSTANCE_ID")
41 .value_or("");
42 zone_a_ =
43 google::cloud::internal::GetEnv("GOOGLE_CLOUD_CPP_BIGTABLE_TEST_ZONE_A")
44 .value_or("");
45 zone_b_ =
46 google::cloud::internal::GetEnv("GOOGLE_CLOUD_CPP_BIGTABLE_TEST_ZONE_B")
47 .value_or("");
48 }
49
SetUp()50 void TableTestEnvironment::SetUp() {
51 ASSERT_FALSE(project_id_.empty());
52 ASSERT_FALSE(instance_id_.empty());
53 ASSERT_FALSE(zone_a_.empty());
54 ASSERT_FALSE(zone_b_.empty());
55
56 using_cloud_bigtable_emulator_ =
57 google::cloud::internal::GetEnv("BIGTABLE_EMULATOR_HOST").has_value();
58
59 generator_ = google::cloud::internal::MakeDefaultPRNG();
60
61 auto admin_client = bigtable::CreateDefaultAdminClient(
62 TableTestEnvironment::project_id(), ClientOptions());
63 auto table_admin =
64 bigtable::TableAdmin(admin_client, TableTestEnvironment::instance_id());
65
66 std::string const family1 = "family1";
67 std::string const family2 = "family2";
68 std::string const family3 = "family3";
69 std::string const family4 = "family4";
70 auto constexpr kTestMaxVersions = 10;
71 auto const test_gc_rule = bigtable::GcRule::MaxNumVersions(kTestMaxVersions);
72 bigtable::TableConfig table_config = bigtable::TableConfig(
73 {
74 {family1, test_gc_rule},
75 {family2, test_gc_rule},
76 {family3, test_gc_rule},
77 {family4, test_gc_rule},
78 },
79 {});
80
81 table_id_ = RandomTableId();
82 ASSERT_STATUS_OK(table_admin.CreateTable(table_id_, table_config));
83 }
84
TearDown()85 void TableTestEnvironment::TearDown() {
86 auto admin_client = bigtable::CreateDefaultAdminClient(
87 TableTestEnvironment::project_id(), ClientOptions());
88 auto table_admin =
89 bigtable::TableAdmin(admin_client, TableTestEnvironment::instance_id());
90
91 ASSERT_STATUS_OK(table_admin.DeleteTable(table_id_));
92 }
93
CreateRandomId(std::string const & prefix,int count)94 std::string TableTestEnvironment::CreateRandomId(std::string const& prefix,
95 int count) {
96 return prefix +
97 google::cloud::internal::Sample(
98 generator_, count, "abcdefghijklmnopqrstuvwxyz0123456789");
99 }
100
RandomTableId()101 std::string TableTestEnvironment::RandomTableId() {
102 // This value was discovered by trial and error, it is not documented in the
103 // proto files.
104 auto constexpr kMaxTableIdLength = 50;
105 static char const kPrefix[] = "table-";
106 static_assert(kMaxTableIdLength > sizeof(kPrefix), "prefix is too long");
107 auto constexpr kSampleCount = kMaxTableIdLength - sizeof(kPrefix) + 1;
108 return CreateRandomId(kPrefix, kSampleCount);
109 }
110
RandomBackupId()111 std::string TableTestEnvironment::RandomBackupId() {
112 // Per google/bigtable/admin/v2/bigtable_table_admin.proto, backup names must
113 // be between 1 and 50 characters such, [_a-zA-Z0-9][-_.a-zA-Z0-9]*.
114 constexpr int kMaxBackupIdLength = 50;
115 static char const kPrefix[] = "backup-";
116 static_assert(kMaxBackupIdLength > sizeof(kPrefix), "prefix is too long");
117 constexpr int kSampleCount = kMaxBackupIdLength - sizeof(kPrefix) + 1;
118 return CreateRandomId(kPrefix, kSampleCount);
119 }
120
RandomInstanceId()121 std::string TableTestEnvironment::RandomInstanceId() {
122 // This value was discovered by trial and error, it is not documented in the
123 // proto files.
124 constexpr int kMaxInstanceIdLength = 33;
125
126 // There are other derived names such as cluster id, display name that uses
127 // returned values, these fields have their limits (e.g. 30 for cluster id)
128 // so need to keep some reserve.
129 constexpr int kReserveForSuffix = 10;
130 static char const kPrefix[] = "inst-";
131 static_assert((kMaxInstanceIdLength - kReserveForSuffix) > sizeof(kPrefix),
132 "prefix is too long");
133 auto constexpr kSampleCount =
134 (kMaxInstanceIdLength - kReserveForSuffix) - sizeof(kPrefix) + 1;
135 return CreateRandomId(kPrefix, kSampleCount);
136 }
137
SetUp()138 void TableIntegrationTest::SetUp() {
139 admin_client_ = bigtable::CreateDefaultAdminClient(
140 TableTestEnvironment::project_id(), ClientOptions());
141 table_admin_ = absl::make_unique<bigtable::TableAdmin>(
142 admin_client_, TableTestEnvironment::instance_id());
143 data_client_ = bigtable::CreateDefaultDataClient(
144 TableTestEnvironment::project_id(), TableTestEnvironment::instance_id(),
145 ClientOptions());
146
147 // In production, we cannot use `DropAllRows()` to cleanup the table because
148 // the integration tests sometimes consume all the 'DropRowRangeGroup' quota.
149 // Instead we delete the rows, when possible, using BulkApply().
150 BulkMutation bulk;
151 auto table = GetTable();
152 // Bigtable does not support more than 100,000 mutations in a BulkMutation.
153 // If we had that many rows then just fallback on DropAllRows(). Most tests
154 // only have a small number of rows, so this is a good strategy to save
155 // DropAllRows() quota, and should be fast in most cases.
156 std::size_t const maximum_mutations = 100000;
157
158 for (auto const& row :
159 table.ReadRows(RowRange::InfiniteRange(), Filter::PassAllFilter())) {
160 if (!row) {
161 break;
162 }
163 bulk.emplace_back(
164 SingleRowMutation(row->row_key(), bigtable::DeleteFromRow()));
165 if (bulk.size() > maximum_mutations) {
166 break;
167 }
168 }
169
170 // The version of the emulator distributed with the Cloud SDK does not handle
171 // deleted rows correctly:
172 // https://github.com/googleapis/google-cloud-go/issues/1240
173 // This has been fixed at HEAD, but the changes have not made it to a version
174 // we can use yet. So just use DropAllRows() in that case.
175 if (bulk.size() > maximum_mutations || UsingCloudBigtableEmulator()) {
176 ASSERT_STATUS_OK(
177 table_admin_->DropAllRows(TableTestEnvironment::table_id()));
178 return;
179 }
180 auto failures = table.BulkApply(std::move(bulk));
181 for (auto&& f : failures) {
182 ASSERT_STATUS_OK(f.status());
183 }
184 }
185
GetTable()186 bigtable::Table TableIntegrationTest::GetTable() {
187 return bigtable::Table(data_client_, TableTestEnvironment::table_id());
188 }
189
ReadRows(bigtable::Table & table,bigtable::Filter filter)190 std::vector<bigtable::Cell> TableIntegrationTest::ReadRows(
191 bigtable::Table& table, bigtable::Filter filter) {
192 auto reader = table.ReadRows(
193 bigtable::RowSet(bigtable::RowRange::InfiniteRange()), std::move(filter));
194 std::vector<bigtable::Cell> result;
195 for (auto const& row : reader) {
196 EXPECT_STATUS_OK(row);
197 std::copy(row->cells().begin(), row->cells().end(),
198 std::back_inserter(result));
199 }
200 return result;
201 }
202
ReadRows(std::string const & table_name,bigtable::Filter filter)203 std::vector<bigtable::Cell> TableIntegrationTest::ReadRows(
204 std::string const& table_name, bigtable::Filter filter) {
205 bigtable::Table table(data_client_, table_name);
206 return ReadRows(table, std::move(filter));
207 }
208
ReadRows(bigtable::Table & table,std::int64_t rows_limit,bigtable::Filter filter)209 std::vector<bigtable::Cell> TableIntegrationTest::ReadRows(
210 bigtable::Table& table, std::int64_t rows_limit, bigtable::Filter filter) {
211 auto reader =
212 table.ReadRows(bigtable::RowSet(bigtable::RowRange::InfiniteRange()),
213 rows_limit, std::move(filter));
214 std::vector<bigtable::Cell> result;
215 for (auto const& row : reader) {
216 EXPECT_STATUS_OK(row);
217 std::copy(row->cells().begin(), row->cells().end(),
218 std::back_inserter(result));
219 }
220 return result;
221 }
222
MoveCellsFromReader(bigtable::RowReader & reader)223 std::vector<bigtable::Cell> TableIntegrationTest::MoveCellsFromReader(
224 bigtable::RowReader& reader) {
225 std::vector<bigtable::Cell> result;
226 for (auto const& row : reader) {
227 EXPECT_STATUS_OK(row);
228 std::move(row->cells().begin(), row->cells().end(),
229 std::back_inserter(result));
230 }
231 return result;
232 }
233
234 /// A helper function to create a list of cells.
CreateCells(bigtable::Table & table,std::vector<bigtable::Cell> const & cells)235 void TableIntegrationTest::CreateCells(
236 bigtable::Table& table, std::vector<bigtable::Cell> const& cells) {
237 std::map<RowKeyType, bigtable::SingleRowMutation> mutations;
238 for (auto const& cell : cells) {
239 using std::chrono::duration_cast;
240 using std::chrono::microseconds;
241 using std::chrono::milliseconds;
242 auto key = cell.row_key();
243 auto inserted = mutations.emplace(key, bigtable::SingleRowMutation(key));
244 inserted.first->second.emplace_back(bigtable::SetCell(
245 cell.family_name(), cell.column_qualifier(),
246 duration_cast<milliseconds>(microseconds(cell.timestamp())),
247 cell.value()));
248 }
249 bigtable::BulkMutation bulk;
250 for (auto& kv : mutations) {
251 bulk.emplace_back(std::move(kv.second));
252 }
253 auto failures = table.BulkApply(std::move(bulk));
254 ASSERT_THAT(failures, ::testing::IsEmpty());
255 }
256
GetCellsIgnoringTimestamp(std::vector<bigtable::Cell> cells)257 std::vector<bigtable::Cell> TableIntegrationTest::GetCellsIgnoringTimestamp(
258 std::vector<bigtable::Cell> cells) {
259 // Create the expected_cells and actual_cells with same timestamp
260 std::vector<bigtable::Cell> return_cells;
261 std::transform(cells.begin(), cells.end(), std::back_inserter(return_cells),
262 [](Cell& cell) {
263 return bigtable::Cell(
264 std::move(cell.row_key()), std::move(cell.family_name()),
265 std::move(cell.column_qualifier()), 0,
266 std::move(cell.value()), std::move(cell.labels()));
267 });
268
269 return return_cells;
270 }
271
CheckEqualUnordered(std::vector<bigtable::Cell> expected,std::vector<bigtable::Cell> actual)272 void TableIntegrationTest::CheckEqualUnordered(
273 std::vector<bigtable::Cell> expected, std::vector<bigtable::Cell> actual) {
274 std::sort(expected.begin(), expected.end());
275 std::sort(actual.begin(), actual.end());
276 EXPECT_THAT(actual, ::testing::ContainerEq(expected));
277 }
278
RandomTableId()279 std::string TableIntegrationTest::RandomTableId() {
280 return TableTestEnvironment::RandomTableId();
281 }
282
project_id()283 std::string TableIntegrationTest::project_id() {
284 return TableTestEnvironment::project_id();
285 }
286
instance_id()287 std::string TableIntegrationTest::instance_id() {
288 return TableTestEnvironment::instance_id();
289 }
290
RandomBackupId()291 std::string TableIntegrationTest::RandomBackupId() {
292 return TableTestEnvironment::RandomBackupId();
293 }
294
295 } // namespace testing
296
CellCompare(bigtable::Cell const & lhs,bigtable::Cell const & rhs)297 int CellCompare(bigtable::Cell const& lhs, bigtable::Cell const& rhs) {
298 auto compare_row_key = internal::CompareRowKey(lhs.row_key(), rhs.row_key());
299 if (compare_row_key != 0) {
300 return compare_row_key;
301 }
302 auto compare_family_name = lhs.family_name().compare(rhs.family_name());
303 if (compare_family_name != 0) {
304 return compare_family_name;
305 }
306 auto compare_column_qualifier = internal::CompareColumnQualifiers(
307 lhs.column_qualifier(), rhs.column_qualifier());
308 if (compare_column_qualifier != 0) {
309 return compare_column_qualifier;
310 }
311 if (lhs.timestamp() < rhs.timestamp()) {
312 return -1;
313 }
314 if (lhs.timestamp() > rhs.timestamp()) {
315 return 1;
316 }
317 auto compare_value = internal::CompareCellValues(lhs.value(), rhs.value());
318 if (compare_value != 0) {
319 return compare_value;
320 }
321 if (lhs.labels() < rhs.labels()) {
322 return -1;
323 }
324 if (lhs.labels() == rhs.labels()) {
325 return 0;
326 }
327 return 1;
328 }
329
330 //@{
331 /// @name Helpers for GTest.
operator ==(Cell const & lhs,Cell const & rhs)332 bool operator==(Cell const& lhs, Cell const& rhs) {
333 return CellCompare(lhs, rhs) == 0;
334 }
335
operator <(Cell const & lhs,Cell const & rhs)336 bool operator<(Cell const& lhs, Cell const& rhs) {
337 return CellCompare(lhs, rhs) < 0;
338 }
339
340 /**
341 * This function is not used in this file, but it is used by GoogleTest; without
342 * it, failing tests will output binary blobs instead of human-readable text.
343 */
PrintTo(bigtable::Cell const & cell,std::ostream * os)344 void PrintTo(bigtable::Cell const& cell, std::ostream* os) {
345 *os << " row_key=" << cell.row_key() << ", family=" << cell.family_name()
346 << ", column=" << cell.column_qualifier()
347 << ", timestamp=" << cell.timestamp().count() << ", value=<"
348 << cell.value() << ">, labels={";
349 char const* del = "";
350 for (auto const& label : cell.labels()) {
351 *os << del << label;
352 del = ",";
353 }
354 *os << "}";
355 }
356 //@}
357
358 } // namespace bigtable
359 } // namespace cloud
360 } // namespace google
361