Aktualizr
C++ SOTA Client
All Classes Namespaces Files Functions Variables Enumerations Enumerator Pages
sqlstorage_test.cc
1 #include <boost/tokenizer.hpp>
2 
3 #include <gtest/gtest.h>
4 
5 #include "logging/logging.h"
6 #include "storage/sql_utils.h"
7 #include "storage/sqlstorage.h"
8 #include "uptane/directorrepository.h"
9 #include "uptane/imagerepository.h"
10 #include "utilities/utils.h"
11 
12 boost::filesystem::path test_data_dir;
13 
14 typedef boost::tokenizer<boost::char_separator<char> > sql_tokenizer;
15 
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;
24 
25  std::string key;
26  std::string value;
27  for (sql_tokenizer::iterator it = tok.begin(); it != tok.end(); ++it) {
28  std::string token = *it;
29  if (value.empty()) {
30  value = token;
31  } else {
32  value = value + " " + token;
33  }
34  switch (parsing_state) {
35  case STATE_INIT:
36  if (token == "CREATE") {
37  parsing_state = STATE_CREATE;
38  } else if (token == "INSERT") {
39  parsing_state = STATE_INSERT;
40  } else {
41  return {};
42  }
43  break;
44  case STATE_CREATE:
45  if (token == "TABLE") {
46  parsing_state = STATE_TABLE;
47  } else if (token == "TRIGGER") {
48  parsing_state = STATE_TRIGGER;
49  } else {
50  return {};
51  }
52  break;
53  case STATE_INSERT:
54  // do not take these into account
55  if (token == ";") {
56  key.clear();
57  value.clear();
58  parsing_state = STATE_INIT;
59  }
60  break;
61  case STATE_TRIGGER:
62  // skip these
63  if (token == "END") {
64  parsing_state = STATE_TRIGGER_END;
65  }
66  break;
67  case STATE_TRIGGER_END:
68  // do not take these into account
69  if (token == ";") {
70  key.clear();
71  value.clear();
72  parsing_state = STATE_INIT;
73  }
74  break;
75  case STATE_TABLE:
76  if (token == "(" || token == ")" || token == "," || token == ";") {
77  return {};
78  }
79  key = token;
80  parsing_state = STATE_NAME;
81  break;
82  case STATE_NAME:
83  if (token == ";") {
84  result[key] = value;
85  key.clear();
86  value.clear();
87  parsing_state = STATE_INIT;
88  }
89  break;
90  default:
91  break;
92  }
93  }
94  return result;
95 }
96 
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);
101 
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;
106  }
107  return (it_l == tokl.end()) && (it_r == tokr.end());
108 }
109 
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";
114  return false;
115  }
116 
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;
123  return false;
124  }
125  }
126  return true;
127 }
128 
129 struct TempSQLDb {
130  std::unique_ptr<TemporaryDirectory> dir;
131  boost::filesystem::path db_path;
132 };
133 
134 static TempSQLDb makeDbWithVersion(DbVersion version) {
135  TempSQLDb tdb;
136  tdb.dir = std_::make_unique<TemporaryDirectory>();
137  tdb.db_path = tdb.dir->Path() / "test.db";
138  SQLite3Guard db(tdb.db_path.c_str());
139 
140  // manual migration runs
141 
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");
145  }
146  }
147 
148  return tdb;
149 }
150 
151 /* Migrate forward through SQL schemas. */
152 TEST(sqlstorage, migrate) {
153  TemporaryDirectory temp_dir;
154  StorageConfig config;
155  config.path = temp_dir.Path();
156 
157  SQLStorage storage(config, false);
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));
162 }
163 
164 /* Migrate backward through SQL schemas. */
165 TEST(sqlstorage, migrate_back) {
166  TemporaryDirectory temp_dir;
167  StorageConfig config;
168  config.path = temp_dir.Path();
169 
170  SQLStorage storage(config, false);
171  auto ver = storage.getVersion();
172 
173  {
174  SQLite3Guard db(temp_dir / "sql.db");
175  std::string migration_script =
176  "\
177  BEGIN TRANSACTION;\
178  CREATE TABLE test_table(test_text TEXT NOT NULL, test_int INT NOT NULL);\
179  INSERT INTO test_table VALUES(\"test_text\", 123);\
180  DELETE FROM version;\
181  INSERT INTO version VALUES( " +
182  std::to_string(static_cast<int>(ver) + 1) +
183  " );\
184  COMMIT TRANSACTION;";
185  db.exec(migration_script, NULL, NULL);
186 
187  std::string back_migration_script =
188  "\
189  DROP TABLE test_table; \
190  DELETE FROM version;\
191  INSERT INTO version VALUES(" +
192  std::to_string(static_cast<int>(ver)) + ");";
193 
194  auto statement = db.prepareStatement("insert into rollback_migrations VALUES (?,?);", static_cast<int>(ver) + 1,
195  back_migration_script);
196  statement.step();
197  }
198 
199  EXPECT_EQ(static_cast<int>(storage.getVersion()), static_cast<int>(ver) + 1);
200  EXPECT_TRUE(storage.dbMigrate());
201  EXPECT_LT(static_cast<int>(storage.getVersion()), static_cast<int>(ver) + 1);
202  EXPECT_TRUE(dbSchemaCheck(storage));
203 }
204 
205 TEST(sqlstorage, rollback_to_15) {
206  TemporaryDirectory temp_dir;
207  StorageConfig config;
208  config.path = temp_dir.Path();
209 
210  SQLStorage storage(config, false);
211 
212  auto ver = storage.getVersion();
213  ASSERT_TRUE(storage.dbMigrateBackward(static_cast<int>(ver), 15));
214  ASSERT_EQ(static_cast<int>(storage.getVersion()), 15);
215 }
216 
217 /* Automatically use latest SQL schema version when initializing database. */
218 TEST(sqlstorage, MigrationVersionCheck) {
219  TemporaryDirectory temp_dir;
220  StorageConfig config;
221  config.path = temp_dir.Path();
222  SQLStorage storage(config, false);
223 
224  EXPECT_EQ(static_cast<int32_t>(storage.getVersion()), libaktualizr_schema_migrations.size() - 1);
225 }
226 
227 /* Reject invalid SQL databases. */
228 TEST(sqlstorage, WrongDatabaseCheck) {
229  TemporaryDirectory temp_dir;
230  StorageConfig config;
231  config.path = temp_dir.Path();
232  {
233  SQLite3Guard db(config.sqldb_path.get(config.path).c_str());
234  if (db.exec("CREATE TABLE some_table(somefield INTEGER);", NULL, NULL) != SQLITE_OK) {
235  FAIL() << "Unable to create an SQL database for testing.";
236  }
237  }
238 
239  EXPECT_THROW(SQLStorage storage(config, false), StorageException);
240 }
241 
242 TEST(sqlstorage, DbMigration7to8) {
243  // it must use raw sql primitives because the SQLStorage object does automatic
244  // migration + the api changes with time
245  auto tdb = makeDbWithVersion(DbVersion(7));
246  SQLite3Guard db(tdb.db_path.c_str());
247 
248  // test migration of `primary_keys` and `device_info`
249  if (db.exec("INSERT INTO primary_keys VALUES ('priv', 'pub');", nullptr, nullptr) != SQLITE_OK) {
250  FAIL();
251  }
252 
253  if (db.exec("INSERT INTO device_info VALUES ('device', 1);", nullptr, nullptr) != SQLITE_OK) {
254  FAIL();
255  }
256 
257  // run migration
258  if (db.exec(libaktualizr_schema_migrations.at(8), nullptr, nullptr) != SQLITE_OK) {
259  FAIL();
260  }
261 
262  // check values
263  auto statement = db.prepareStatement("SELECT private, public FROM primary_keys;");
264  if (statement.step() != SQLITE_ROW) {
265  FAIL();
266  }
267 
268  EXPECT_EQ(statement.get_result_col_str(0).value(), "priv");
269  EXPECT_EQ(statement.get_result_col_str(1).value(), "pub");
270 
271  statement = db.prepareStatement("SELECT device_id, is_registered FROM device_info;");
272  if (statement.step() != SQLITE_ROW) {
273  FAIL();
274  }
275 
276  EXPECT_EQ(statement.get_result_col_str(0).value(), "device");
277  EXPECT_EQ(statement.get_result_col_int(1), 1);
278 }
279 
280 TEST(sqlstorage, DbMigration12to13) {
281  // it must use raw sql primitives because the SQLStorage object does automatic
282  // migration + the api changes with time
283  auto tdb = makeDbWithVersion(DbVersion(12));
284  SQLite3Guard db(tdb.db_path.c_str());
285 
286  // test migration of installed_versions
287  if (db.exec("INSERT INTO ecu_serials VALUES ('primary_ecu', 'primary_hw', 1);", nullptr, nullptr) != SQLITE_OK) {
288  FAIL();
289  }
290 
291  if (db.exec("INSERT INTO ecu_serials VALUES ('secondary_ecu', 'secondary_hw', 0);", nullptr, nullptr) != SQLITE_OK) {
292  FAIL();
293  }
294 
295  if (db.exec("INSERT INTO installed_versions VALUES ('sha256', 'v1', 1, 2);", nullptr, nullptr) != SQLITE_OK) {
296  FAIL();
297  }
298 
299  // run migration
300  if (db.exec(libaktualizr_schema_migrations.at(13), nullptr, nullptr) != SQLITE_OK) {
301  std::cout << db.errmsg() << "\n";
302  FAIL() << "Migration 12 to 13 failed";
303  }
304 
305  // check values
306  auto statement = db.prepareStatement(
307  "SELECT ecu_serial, sha256, name, hashes, length, is_current, is_pending FROM installed_versions;");
308  if (statement.step() != SQLITE_ROW) {
309  FAIL() << "installed_versions is empty";
310  }
311 
312  EXPECT_EQ(statement.get_result_col_str(0).value(), "primary_ecu");
313  EXPECT_EQ(statement.get_result_col_str(1).value(), "sha256");
314  EXPECT_EQ(statement.get_result_col_str(2).value(), "v1");
315  EXPECT_EQ(statement.get_result_col_str(3).value(), "");
316  EXPECT_EQ(statement.get_result_col_int(4), 2);
317  EXPECT_EQ(statement.get_result_col_int(5), 1);
318  EXPECT_EQ(statement.get_result_col_int(6), 0);
319 
320  if (statement.step() != SQLITE_DONE) {
321  FAIL() << "Too many rows";
322  }
323 }
324 
325 TEST(sqlstorage, DbMigration18to19) {
326  // it must use raw sql primitives because the SQLStorage object does automatic
327  // migration + the api changes with time
328  auto tdb = makeDbWithVersion(DbVersion(18));
329  SQLite3Guard db(tdb.db_path.c_str());
330 
331  // test migration of ecu_serials (wrong order is intended)
332  if (db.exec("INSERT INTO ecu_serials VALUES ('secondary_ecu', 'secondary_hw', 0);", nullptr, nullptr) != SQLITE_OK) {
333  FAIL();
334  }
335 
336  if (db.exec("INSERT INTO ecu_serials VALUES ('primary_ecu', 'primary_hw', 1);", nullptr, nullptr) != SQLITE_OK) {
337  FAIL();
338  }
339 
340  // run migration
341  if (db.exec(libaktualizr_schema_migrations.at(19), nullptr, nullptr) != SQLITE_OK) {
342  std::cout << db.errmsg() << "\n";
343  FAIL() << "Migration 18 to 19 failed";
344  }
345 
346  // check values
347  auto statement = db.prepareStatement("SELECT serial, hardware_id, is_primary FROM ecu_serials ORDER BY id;");
348  if (statement.step() != SQLITE_ROW) {
349  FAIL() << "ecu_serials is empty";
350  }
351 
352  EXPECT_EQ(statement.get_result_col_str(0).value(), "primary_ecu");
353  EXPECT_EQ(statement.get_result_col_str(1).value(), "primary_hw");
354  EXPECT_EQ(statement.get_result_col_int(2), 1);
355 
356  if (statement.step() != SQLITE_ROW) {
357  FAIL() << "ecu_serials contains only one element";
358  }
359 
360  EXPECT_EQ(statement.get_result_col_str(0).value(), "secondary_ecu");
361  EXPECT_EQ(statement.get_result_col_str(1).value(), "secondary_hw");
362  EXPECT_EQ(statement.get_result_col_int(2), 0);
363 
364  if (statement.step() != SQLITE_DONE) {
365  FAIL() << "Too many rows";
366  }
367 }
368 
369 TEST(sqlstorage, DbMigration19to20) {
370  // it must use raw sql primitives because the SQLStorage object does automatic
371  // migration + the api changes with time
372  auto tdb = makeDbWithVersion(DbVersion(19));
373  SQLite3Guard db(tdb.db_path.c_str());
374 
375  if (db.exec("INSERT INTO ecu_serials(serial,hardware_id,is_primary) VALUES ('primary_ecu', 'primary_hw', 1);",
376  nullptr, nullptr) != SQLITE_OK) {
377  FAIL();
378  }
379 
380  if (db.exec("INSERT INTO installed_versions VALUES ('primary_ecu', 'shav1', 'v1', 'sha256:shav1', 2, 'cor1', 0, 0);",
381  nullptr, nullptr) != SQLITE_OK) {
382  FAIL();
383  }
384  if (db.exec("INSERT INTO installed_versions VALUES ('primary_ecu', 'shav2', 'v2', 'sha256:shav2', 3, 'cor2', 1, 0);",
385  nullptr, nullptr) != SQLITE_OK) {
386  FAIL();
387  }
388 
389  // run migration
390  if (db.exec(libaktualizr_schema_migrations.at(20), nullptr, nullptr) != SQLITE_OK) {
391  std::cout << db.errmsg() << "\n";
392  FAIL() << "Migration 19 to 20 failed";
393  }
394 
395  // check values
396  auto statement = db.prepareStatement(
397  "SELECT ecu_serial, sha256, name, hashes, length, correlation_id, is_current, is_pending, "
398  "was_installed FROM installed_versions ORDER BY id;");
399  if (statement.step() != SQLITE_ROW) {
400  FAIL() << "installed_versions is empty";
401  }
402 
403  EXPECT_EQ(statement.get_result_col_str(0).value(), "primary_ecu");
404  EXPECT_EQ(statement.get_result_col_str(1).value(), "shav1");
405  EXPECT_EQ(statement.get_result_col_str(2).value(), "v1");
406  EXPECT_EQ(statement.get_result_col_str(3).value(), "sha256:shav1");
407  EXPECT_EQ(statement.get_result_col_int(4), 2);
408  EXPECT_EQ(statement.get_result_col_str(5).value(), "cor1");
409  EXPECT_EQ(statement.get_result_col_int(6), 0);
410  EXPECT_EQ(statement.get_result_col_int(7), 0);
411  EXPECT_EQ(statement.get_result_col_int(8), 1);
412 
413  if (statement.step() != SQLITE_ROW) {
414  FAIL() << "installed_versions contains only one element";
415  }
416 
417  EXPECT_EQ(statement.get_result_col_str(0).value(), "primary_ecu");
418  EXPECT_EQ(statement.get_result_col_str(1).value(), "shav2");
419  EXPECT_EQ(statement.get_result_col_str(2).value(), "v2");
420  EXPECT_EQ(statement.get_result_col_str(3).value(), "sha256:shav2");
421  EXPECT_EQ(statement.get_result_col_int(4), 3);
422  EXPECT_EQ(statement.get_result_col_str(5).value(), "cor2");
423  EXPECT_EQ(statement.get_result_col_int(6), 1);
424  EXPECT_EQ(statement.get_result_col_int(7), 0);
425  EXPECT_EQ(statement.get_result_col_int(8), 1);
426 
427  if (statement.step() != SQLITE_DONE) {
428  FAIL() << "Too many rows";
429  }
430 }
431 
432 /**
433  * Check that old metadata is still valid
434  */
435 TEST(sqlstorage, migrate_root_works) {
436  TemporaryDirectory temp_dir;
437  StorageConfig config;
438  config.path = temp_dir.Path();
439 
440  boost::filesystem::remove_all(config.sqldb_path.get(config.path));
441  boost::filesystem::copy(test_data_dir / "version5.sql", config.sqldb_path.get(config.path));
442  SQLStorage storage(config, false);
443 
444  EXPECT_TRUE(storage.dbMigrate());
445  EXPECT_TRUE(dbSchemaCheck(storage));
446 
447  // Director
448  std::string raw_director_root;
449  storage.loadRoot(&raw_director_root, Uptane::RepositoryType::Director(), Uptane::Version());
451  EXPECT_NO_THROW(director.initRoot(Uptane::RepositoryType(Uptane::RepositoryType::DIRECTOR), raw_director_root));
452 
453  std::string raw_director_targets;
454  storage.loadNonRoot(&raw_director_targets, Uptane::RepositoryType::Director(), Uptane::Role::Targets());
455 
456  EXPECT_NO_THROW(director.verifyTargets(raw_director_targets));
457 
458  // Image repo
459  std::string raw_image_root;
460  storage.loadRoot(&raw_image_root, Uptane::RepositoryType::Image(), Uptane::Version());
461  Uptane::ImageRepository imagerepository;
462  EXPECT_NO_THROW(imagerepository.initRoot(Uptane::RepositoryType(Uptane::RepositoryType::IMAGE), raw_image_root));
463 
464  // Check that the roots are different and haven't been swapped
465  EXPECT_NE(raw_director_root, raw_image_root);
466  Json::Value director_json = Utils::parseJSON(raw_director_root);
467  Json::Value sign = director_json["signed"];
468  EXPECT_EQ(sign["_type"], "Root");
469  EXPECT_TRUE(sign["keys"].isMember("1ba3b2932863c0c6e5ff857ecdeb476b69b8b9f9ba4e36723eb10faf7768818b"));
470 }
471 
472 /* Migrate from the legacy filesystem storage. */
473 TEST(sqlstorage, migrate_from_fs) {
474  TemporaryDirectory temp_dir;
475  StorageConfig config;
476  config.path = temp_dir.Path() / "config";
477 
478  // copy a snapshot from a real provisioned device using fs storage
479  // (anonymized data)
480  Utils::copyDir(test_data_dir / "fs_snapshot", config.path);
481  ASSERT_GE(chmod(config.path.c_str(), S_IRWXU), 0);
482 
483  {
484  auto storage = INvStorage::newStorage(config);
485 
486  EXPECT_TRUE(storage->loadPrimaryKeys(nullptr, nullptr));
487  EXPECT_TRUE(storage->loadTlsCreds(nullptr, nullptr, nullptr));
488  EXPECT_TRUE(storage->loadLatestRoot(nullptr, Uptane::RepositoryType::Director()));
489  EXPECT_TRUE(storage->loadDeviceId(nullptr));
490 
491  EcuSerials serials;
492  EXPECT_TRUE(storage->loadEcuSerials(&serials));
493  EXPECT_EQ(serials.size(), 3);
494 
495  std::vector<MisconfiguredEcu> misconfigured;
496  EXPECT_TRUE(storage->loadMisconfiguredEcus(&misconfigured));
497  EXPECT_EQ(misconfigured.size(), 1);
498 
499  EXPECT_TRUE(storage->loadEcuRegistered());
500 
501  boost::optional<Uptane::Target> installed;
502  storage->loadPrimaryInstalledVersions(&installed, nullptr);
503  EXPECT_TRUE(!!installed);
504  }
505 
506  // note: installation result is not migrated anymore
507 
508  boost::filesystem::recursive_directory_iterator repo_dir_it(config.path), repo_dir_end;
509  for (; repo_dir_it != repo_dir_end; ++repo_dir_it) {
510  const auto p = repo_dir_it->path();
511  if (p == config.sqldb_path.get(config.path)) {
512  continue;
513  }
514  FAIL() << p << " should not exist anymore";
515  }
516 }
517 
518 #ifndef __NO_MAIN__
519 int main(int argc, char** argv) {
520  ::testing::InitGoogleTest(&argc, argv);
521  logger_init();
522  logger_set_threshold(boost::log::trivial::trace);
523  if (argc != 2) {
524  std::cout << "Please pass the directory containing sql migration scripts as the first argument\n";
525  return 1;
526  }
527 
528  test_data_dir = argv[1];
529 
530  if (!boost::filesystem::is_directory(test_data_dir)) {
531  std::cout << test_data_dir << " is not a directory\n";
532  return 1;
533  }
534 
535  return RUN_ALL_TESTS();
536 }
537 #endif
Uptane::DirectorRepository
Definition: directorrepository.h:13
Uptane::Version
Metadata version numbers.
Definition: tuf.h:120
StorageException
Definition: storage_exception.h:4
Uptane::ImageRepository
Definition: imagerepository.h:13
StorageConfig
Definition: config.h:111
Uptane::RepositoryType
Definition: tuf.h:21
TempSQLDb
Definition: sqlstorage_test.cc:129
TemporaryDirectory
Definition: utils.h:82
result
Results of libaktualizr API calls.
Definition: results.h:12
SQLite3Guard
Definition: sql_utils.h:131
SQLStorage
Definition: sqlstorage.h:18