1 #include <boost/tokenizer.hpp>
3 #include <gtest/gtest.h>
5 #include "logging/logging.h"
6 #include "storage/sql_utils.h"
7 #include "storage/sqlstorage.h"
8 #include "uptane/directorrepository.h"
9 #include "uptane/imagesrepository.h"
10 #include "utilities/utils.h"
12 boost::filesystem::path test_data_dir;
14 typedef boost::tokenizer<boost::char_separator<char> > sql_tokenizer;
16 static std::map<std::string, std::string> parseSchema() {
17 std::map<std::string, std::string>
result;
18 std::vector<std::string> tokens;
19 enum { STATE_INIT, STATE_CREATE, STATE_INSERT, STATE_TABLE, STATE_NAME, STATE_TRIGGER, STATE_TRIGGER_END };
20 boost::char_separator<char> sep(
" \"\t\r\n",
"(),;");
21 std::string schema(libaktualizr_current_schema);
22 sql_tokenizer tok(schema, sep);
23 int parsing_state = STATE_INIT;
27 for (sql_tokenizer::iterator it = tok.begin(); it != tok.end(); ++it) {
28 std::string token = *it;
32 value = value +
" " + token;
34 switch (parsing_state) {
36 if (token ==
"CREATE") {
37 parsing_state = STATE_CREATE;
38 }
else if (token ==
"INSERT") {
39 parsing_state = STATE_INSERT;
45 if (token ==
"TABLE") {
46 parsing_state = STATE_TABLE;
47 }
else if (token ==
"TRIGGER") {
48 parsing_state = STATE_TRIGGER;
58 parsing_state = STATE_INIT;
64 parsing_state = STATE_TRIGGER_END;
67 case STATE_TRIGGER_END:
72 parsing_state = STATE_INIT;
76 if (token ==
"(" || token ==
")" || token ==
"," || token ==
";") {
80 parsing_state = STATE_NAME;
87 parsing_state = STATE_INIT;
97 static bool tableSchemasEqual(
const std::string& left,
const std::string& right) {
98 boost::char_separator<char> sep(
" \"\t\r\n",
"(),;");
99 sql_tokenizer tokl(left, sep);
100 sql_tokenizer tokr(right, sep);
102 sql_tokenizer::iterator it_l;
103 sql_tokenizer::iterator it_r;
104 for (it_l = tokl.begin(), it_r = tokr.begin(); it_l != tokl.end() && it_r != tokr.end(); ++it_l, ++it_r) {
105 if (*it_l != *it_r)
return false;
107 return (it_l == tokl.end()) && (it_r == tokr.end());
110 static bool dbSchemaCheck(
SQLStorage& storage) {
111 std::map<std::string, std::string> tables = parseSchema();
112 if (tables.empty()) {
113 LOG_ERROR <<
"Could not parse schema";
117 for (
auto it = tables.begin(); it != tables.end(); ++it) {
118 std::string schema_from_db = storage.getTableSchemaFromDb(it->first);
119 if (!tableSchemasEqual(schema_from_db, it->second)) {
120 LOG_ERROR <<
"Schemas don't match for " << it->first;
121 LOG_ERROR <<
"Expected " << it->second;
122 LOG_ERROR <<
"Found " << schema_from_db;
130 std::unique_ptr<TemporaryDirectory> dir;
131 boost::filesystem::path db_path;
134 static TempSQLDb makeDbWithVersion(DbVersion version) {
136 tdb.dir = std_::make_unique<TemporaryDirectory>();
137 tdb.db_path = tdb.dir->Path() /
"test.db";
142 for (uint32_t k = 0; k <= static_cast<uint32_t>(version); k++) {
143 if (db.exec(libaktualizr_schema_migrations.at(k),
nullptr,
nullptr) != SQLITE_OK) {
144 throw std::runtime_error(
"Migration run failed");
152 TEST(sqlstorage, migrate) {
155 config.path = temp_dir.Path();
158 boost::filesystem::remove_all(config.sqldb_path.get(config.path));
159 EXPECT_FALSE(dbSchemaCheck(storage));
160 EXPECT_TRUE(storage.dbMigrate());
161 EXPECT_TRUE(dbSchemaCheck(storage));
165 TEST(sqlstorage, migrate_back) {
168 config.path = temp_dir.Path();
173 auto ver = storage.getVersion();
174 std::string migration_script =
177 CREATE TABLE test_table(test_text TEXT NOT NULL, test_int INT NOT NULL);\
178 INSERT INTO test_table VALUES(\"test_text\", 123);\
179 DELETE FROM version;\
180 INSERT INTO version VALUES( " +
181 std::to_string(static_cast<int>(ver) + 1) +
183 COMMIT TRANSACTION;";
184 db.exec(migration_script, NULL, NULL);
186 std::string back_migration_script =
188 DROP TABLE test_table; \
189 DELETE FROM version;\
190 INSERT INTO version VALUES(" +
191 std::to_string(static_cast<int>(ver)) +
");";
193 auto statement = db.prepareStatement(
"insert into rollback_migrations VALUES (?,?);", static_cast<int>(ver) + 1,
194 back_migration_script);
197 EXPECT_EQ(static_cast<int>(storage.getVersion()), static_cast<int>(ver) + 1);
198 EXPECT_TRUE(storage.dbMigrate());
199 EXPECT_LT(static_cast<int>(storage.getVersion()), static_cast<int>(ver) + 1);
200 EXPECT_TRUE(dbSchemaCheck(storage));
203 TEST(sqlstorage, rollback_to_15) {
206 config.path = temp_dir.Path();
210 auto ver = storage.getVersion();
211 ASSERT_TRUE(storage.dbMigrateBackward(static_cast<int>(ver), 15));
212 ASSERT_EQ(static_cast<int>(storage.getVersion()), 15);
216 TEST(sqlstorage, MigrationVersionCheck) {
219 config.path = temp_dir.Path();
222 EXPECT_EQ(static_cast<int32_t>(storage.getVersion()), libaktualizr_schema_migrations.size() - 1);
226 TEST(sqlstorage, WrongDatabaseCheck) {
229 config.path = temp_dir.Path();
231 SQLite3Guard db(config.sqldb_path.get(config.path).c_str());
232 if (db.exec(
"CREATE TABLE some_table(somefield INTEGER);", NULL, NULL) != SQLITE_OK) {
233 FAIL() <<
"Unable to create an SQL database for testing.";
240 TEST(sqlstorage, DbMigration7to8) {
243 auto tdb = makeDbWithVersion(DbVersion(7));
247 if (db.exec(
"INSERT INTO primary_keys VALUES ('priv', 'pub');",
nullptr,
nullptr) != SQLITE_OK) {
251 if (db.exec(
"INSERT INTO device_info VALUES ('device', 1);",
nullptr,
nullptr) != SQLITE_OK) {
256 if (db.exec(libaktualizr_schema_migrations.at(8),
nullptr,
nullptr) != SQLITE_OK) {
261 auto statement = db.prepareStatement(
"SELECT private, public FROM primary_keys;");
262 if (statement.step() != SQLITE_ROW) {
266 EXPECT_EQ(statement.get_result_col_str(0).value(),
"priv");
267 EXPECT_EQ(statement.get_result_col_str(1).value(),
"pub");
269 statement = db.prepareStatement(
"SELECT device_id, is_registered FROM device_info;");
270 if (statement.step() != SQLITE_ROW) {
274 EXPECT_EQ(statement.get_result_col_str(0).value(),
"device");
275 EXPECT_EQ(statement.get_result_col_int(1), 1);
278 TEST(sqlstorage, DbMigration12to13) {
281 auto tdb = makeDbWithVersion(DbVersion(12));
285 if (db.exec(
"INSERT INTO ecu_serials VALUES ('primary_ecu', 'primary_hw', 1);",
nullptr,
nullptr) != SQLITE_OK) {
289 if (db.exec(
"INSERT INTO ecu_serials VALUES ('secondary_ecu', 'secondary_hw', 0);",
nullptr,
nullptr) != SQLITE_OK) {
293 if (db.exec(
"INSERT INTO installed_versions VALUES ('sha256', 'v1', 1, 2);",
nullptr,
nullptr) != SQLITE_OK) {
298 if (db.exec(libaktualizr_schema_migrations.at(13),
nullptr,
nullptr) != SQLITE_OK) {
299 std::cout << db.errmsg() <<
"\n";
300 FAIL() <<
"Migration 12 to 13 failed";
304 auto statement = db.prepareStatement(
305 "SELECT ecu_serial, sha256, name, hashes, length, is_current, is_pending FROM installed_versions;");
306 if (statement.step() != SQLITE_ROW) {
307 FAIL() <<
"installed_versions is empty";
310 EXPECT_EQ(statement.get_result_col_str(0).value(),
"primary_ecu");
311 EXPECT_EQ(statement.get_result_col_str(1).value(),
"sha256");
312 EXPECT_EQ(statement.get_result_col_str(2).value(),
"v1");
313 EXPECT_EQ(statement.get_result_col_str(3).value(),
"");
314 EXPECT_EQ(statement.get_result_col_int(4), 2);
315 EXPECT_EQ(statement.get_result_col_int(5), 1);
316 EXPECT_EQ(statement.get_result_col_int(6), 0);
318 if (statement.step() != SQLITE_DONE) {
319 FAIL() <<
"Too many rows";
323 TEST(sqlstorage, DbMigration18to19) {
326 auto tdb = makeDbWithVersion(DbVersion(18));
330 if (db.exec(
"INSERT INTO ecu_serials VALUES ('secondary_ecu', 'secondary_hw', 0);",
nullptr,
nullptr) != SQLITE_OK) {
334 if (db.exec(
"INSERT INTO ecu_serials VALUES ('primary_ecu', 'primary_hw', 1);",
nullptr,
nullptr) != SQLITE_OK) {
339 if (db.exec(libaktualizr_schema_migrations.at(19),
nullptr,
nullptr) != SQLITE_OK) {
340 std::cout << db.errmsg() <<
"\n";
341 FAIL() <<
"Migration 18 to 19 failed";
345 auto statement = db.prepareStatement(
"SELECT serial, hardware_id, is_primary FROM ecu_serials ORDER BY id;");
346 if (statement.step() != SQLITE_ROW) {
347 FAIL() <<
"ecu_serials is empty";
350 EXPECT_EQ(statement.get_result_col_str(0).value(),
"primary_ecu");
351 EXPECT_EQ(statement.get_result_col_str(1).value(),
"primary_hw");
352 EXPECT_EQ(statement.get_result_col_int(2), 1);
354 if (statement.step() != SQLITE_ROW) {
355 FAIL() <<
"ecu_serials contains only one element";
358 EXPECT_EQ(statement.get_result_col_str(0).value(),
"secondary_ecu");
359 EXPECT_EQ(statement.get_result_col_str(1).value(),
"secondary_hw");
360 EXPECT_EQ(statement.get_result_col_int(2), 0);
362 if (statement.step() != SQLITE_DONE) {
363 FAIL() <<
"Too many rows";
367 TEST(sqlstorage, DbMigration19to20) {
370 auto tdb = makeDbWithVersion(DbVersion(19));
373 if (db.exec(
"INSERT INTO ecu_serials(serial,hardware_id,is_primary) VALUES ('primary_ecu', 'primary_hw', 1);",
374 nullptr,
nullptr) != SQLITE_OK) {
378 if (db.exec(
"INSERT INTO installed_versions VALUES ('primary_ecu', 'shav1', 'v1', 'sha256:shav1', 2, 'cor1', 0, 0);",
379 nullptr,
nullptr) != SQLITE_OK) {
382 if (db.exec(
"INSERT INTO installed_versions VALUES ('primary_ecu', 'shav2', 'v2', 'sha256:shav2', 3, 'cor2', 1, 0);",
383 nullptr,
nullptr) != SQLITE_OK) {
388 if (db.exec(libaktualizr_schema_migrations.at(20),
nullptr,
nullptr) != SQLITE_OK) {
389 std::cout << db.errmsg() <<
"\n";
390 FAIL() <<
"Migration 19 to 20 failed";
394 auto statement = db.prepareStatement(
395 "SELECT ecu_serial, sha256, name, hashes, length, correlation_id, is_current, is_pending, "
396 "was_installed FROM installed_versions ORDER BY id;");
397 if (statement.step() != SQLITE_ROW) {
398 FAIL() <<
"installed_versions is empty";
401 EXPECT_EQ(statement.get_result_col_str(0).value(),
"primary_ecu");
402 EXPECT_EQ(statement.get_result_col_str(1).value(),
"shav1");
403 EXPECT_EQ(statement.get_result_col_str(2).value(),
"v1");
404 EXPECT_EQ(statement.get_result_col_str(3).value(),
"sha256:shav1");
405 EXPECT_EQ(statement.get_result_col_int(4), 2);
406 EXPECT_EQ(statement.get_result_col_str(5).value(),
"cor1");
407 EXPECT_EQ(statement.get_result_col_int(6), 0);
408 EXPECT_EQ(statement.get_result_col_int(7), 0);
409 EXPECT_EQ(statement.get_result_col_int(8), 1);
411 if (statement.step() != SQLITE_ROW) {
412 FAIL() <<
"installed_versions contains only one element";
415 EXPECT_EQ(statement.get_result_col_str(0).value(),
"primary_ecu");
416 EXPECT_EQ(statement.get_result_col_str(1).value(),
"shav2");
417 EXPECT_EQ(statement.get_result_col_str(2).value(),
"v2");
418 EXPECT_EQ(statement.get_result_col_str(3).value(),
"sha256:shav2");
419 EXPECT_EQ(statement.get_result_col_int(4), 3);
420 EXPECT_EQ(statement.get_result_col_str(5).value(),
"cor2");
421 EXPECT_EQ(statement.get_result_col_int(6), 1);
422 EXPECT_EQ(statement.get_result_col_int(7), 0);
423 EXPECT_EQ(statement.get_result_col_int(8), 1);
425 if (statement.step() != SQLITE_DONE) {
426 FAIL() <<
"Too many rows";
433 TEST(sqlstorage, migrate_root_works) {
436 config.path = temp_dir.Path();
438 boost::filesystem::remove_all(config.sqldb_path.get(config.path));
439 boost::filesystem::copy(test_data_dir /
"version5.sql", config.sqldb_path.get(config.path));
442 EXPECT_TRUE(storage.dbMigrate());
443 EXPECT_TRUE(dbSchemaCheck(storage));
446 std::string raw_director_root;
447 storage.loadRoot(&raw_director_root, Uptane::RepositoryType::Director(),
Uptane::Version());
449 EXPECT_TRUE(director.initRoot(raw_director_root));
451 std::string raw_director_targets;
452 storage.loadNonRoot(&raw_director_targets, Uptane::RepositoryType::Director(), Uptane::Role::Targets());
454 EXPECT_TRUE(director.verifyTargets(raw_director_targets));
457 std::string raw_images_root;
458 storage.loadRoot(&raw_images_root, Uptane::RepositoryType::Image(),
Uptane::Version());
460 EXPECT_TRUE(imagesrepository.initRoot(raw_images_root));
463 EXPECT_NE(raw_director_root, raw_images_root);
464 Json::Value director_json = Utils::parseJSON(raw_director_root);
465 Json::Value sign = director_json[
"signed"];
466 EXPECT_EQ(sign[
"_type"],
"Root");
467 EXPECT_TRUE(sign[
"keys"].isMember(
"1ba3b2932863c0c6e5ff857ecdeb476b69b8b9f9ba4e36723eb10faf7768818b"));
471 TEST(sqlstorage, migrate_from_fs) {
474 config.path = temp_dir.Path() /
"config";
478 Utils::copyDir(test_data_dir /
"fs_snapshot", config.path);
479 ASSERT_GE(chmod(config.path.c_str(), S_IRWXU), 0);
482 auto storage = INvStorage::newStorage(config);
484 EXPECT_TRUE(storage->loadPrimaryKeys(
nullptr,
nullptr));
485 EXPECT_TRUE(storage->loadTlsCreds(
nullptr,
nullptr,
nullptr));
486 EXPECT_TRUE(storage->loadLatestRoot(
nullptr, Uptane::RepositoryType::Director()));
487 EXPECT_TRUE(storage->loadDeviceId(
nullptr));
490 EXPECT_TRUE(storage->loadEcuSerials(&serials));
491 EXPECT_EQ(serials.size(), 3);
493 std::vector<MisconfiguredEcu> misconfigured;
494 EXPECT_TRUE(storage->loadMisconfiguredEcus(&misconfigured));
495 EXPECT_EQ(misconfigured.size(), 1);
497 EXPECT_TRUE(storage->loadEcuRegistered());
499 boost::optional<Uptane::Target> installed;
500 storage->loadPrimaryInstalledVersions(&installed,
nullptr);
501 EXPECT_TRUE(!!installed);
506 boost::filesystem::recursive_directory_iterator repo_dir_it(config.path), repo_dir_end;
507 for (; repo_dir_it != repo_dir_end; ++repo_dir_it) {
508 const auto p = repo_dir_it->path();
509 if (p == config.sqldb_path.get(config.path)) {
512 FAIL() << p <<
" should not exist anymore";
517 int main(
int argc,
char** argv) {
518 ::testing::InitGoogleTest(&argc, argv);
520 logger_set_threshold(boost::log::trivial::trace);
522 std::cout <<
"Please pass the directory containing sql migration scripts as the first argument\n";
526 test_data_dir = argv[1];
528 if (!boost::filesystem::is_directory(test_data_dir)) {
529 std::cout << test_data_dir <<
" is not a directory\n";
533 return RUN_ALL_TESTS();