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/imagesrepository.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 
172  SQLite3Guard db(temp_dir / "sql.db");
173  auto ver = storage.getVersion();
174  std::string migration_script =
175  "\
176  BEGIN TRANSACTION;\
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) +
182  " );\
183  COMMIT TRANSACTION;";
184  db.exec(migration_script, NULL, NULL);
185 
186  std::string back_migration_script =
187  "\
188  DROP TABLE test_table; \
189  DELETE FROM version;\
190  INSERT INTO version VALUES(" +
191  std::to_string(static_cast<int>(ver)) + ");";
192 
193  auto statement = db.prepareStatement("insert into rollback_migrations VALUES (?,?);", static_cast<int>(ver) + 1,
194  back_migration_script);
195  statement.step();
196 
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));
201 }
202 
203 TEST(sqlstorage, rollback_to_15) {
204  TemporaryDirectory temp_dir;
205  StorageConfig config;
206  config.path = temp_dir.Path();
207 
208  SQLStorage storage(config, false);
209 
210  auto ver = storage.getVersion();
211  ASSERT_TRUE(storage.dbMigrateBackward(static_cast<int>(ver), 15));
212  ASSERT_EQ(static_cast<int>(storage.getVersion()), 15);
213 }
214 
215 /* Automatically use latest SQL schema version when initializing database. */
216 TEST(sqlstorage, MigrationVersionCheck) {
217  TemporaryDirectory temp_dir;
218  StorageConfig config;
219  config.path = temp_dir.Path();
220  SQLStorage storage(config, false);
221 
222  EXPECT_EQ(static_cast<int32_t>(storage.getVersion()), libaktualizr_schema_migrations.size() - 1);
223 }
224 
225 /* Reject invalid SQL databases. */
226 TEST(sqlstorage, WrongDatabaseCheck) {
227  TemporaryDirectory temp_dir;
228  StorageConfig config;
229  config.path = temp_dir.Path();
230  {
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.";
234  }
235  }
236 
237  EXPECT_THROW(SQLStorage storage(config, false), StorageException);
238 }
239 
240 TEST(sqlstorage, DbMigration7to8) {
241  // it must use raw sql primitives because the SQLStorage object does automatic
242  // migration + the api changes with time
243  auto tdb = makeDbWithVersion(DbVersion(7));
244  SQLite3Guard db(tdb.db_path.c_str());
245 
246  // test migration of `primary_keys` and `device_info`
247  if (db.exec("INSERT INTO primary_keys VALUES ('priv', 'pub');", nullptr, nullptr) != SQLITE_OK) {
248  FAIL();
249  }
250 
251  if (db.exec("INSERT INTO device_info VALUES ('device', 1);", nullptr, nullptr) != SQLITE_OK) {
252  FAIL();
253  }
254 
255  // run migration
256  if (db.exec(libaktualizr_schema_migrations.at(8), nullptr, nullptr) != SQLITE_OK) {
257  FAIL();
258  }
259 
260  // check values
261  auto statement = db.prepareStatement("SELECT private, public FROM primary_keys;");
262  if (statement.step() != SQLITE_ROW) {
263  FAIL();
264  }
265 
266  EXPECT_EQ(statement.get_result_col_str(0).value(), "priv");
267  EXPECT_EQ(statement.get_result_col_str(1).value(), "pub");
268 
269  statement = db.prepareStatement("SELECT device_id, is_registered FROM device_info;");
270  if (statement.step() != SQLITE_ROW) {
271  FAIL();
272  }
273 
274  EXPECT_EQ(statement.get_result_col_str(0).value(), "device");
275  EXPECT_EQ(statement.get_result_col_int(1), 1);
276 }
277 
278 TEST(sqlstorage, DbMigration12to13) {
279  // it must use raw sql primitives because the SQLStorage object does automatic
280  // migration + the api changes with time
281  auto tdb = makeDbWithVersion(DbVersion(12));
282  SQLite3Guard db(tdb.db_path.c_str());
283 
284  // test migration of installed_versions
285  if (db.exec("INSERT INTO ecu_serials VALUES ('primary_ecu', 'primary_hw', 1);", nullptr, nullptr) != SQLITE_OK) {
286  FAIL();
287  }
288 
289  if (db.exec("INSERT INTO ecu_serials VALUES ('secondary_ecu', 'secondary_hw', 0);", nullptr, nullptr) != SQLITE_OK) {
290  FAIL();
291  }
292 
293  if (db.exec("INSERT INTO installed_versions VALUES ('sha256', 'v1', 1, 2);", nullptr, nullptr) != SQLITE_OK) {
294  FAIL();
295  }
296 
297  // run migration
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";
301  }
302 
303  // check values
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";
308  }
309 
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);
317 
318  if (statement.step() != SQLITE_DONE) {
319  FAIL() << "Too many rows";
320  }
321 }
322 
323 TEST(sqlstorage, DbMigration18to19) {
324  // it must use raw sql primitives because the SQLStorage object does automatic
325  // migration + the api changes with time
326  auto tdb = makeDbWithVersion(DbVersion(18));
327  SQLite3Guard db(tdb.db_path.c_str());
328 
329  // test migration of ecu_serials (wrong order is intended)
330  if (db.exec("INSERT INTO ecu_serials VALUES ('secondary_ecu', 'secondary_hw', 0);", nullptr, nullptr) != SQLITE_OK) {
331  FAIL();
332  }
333 
334  if (db.exec("INSERT INTO ecu_serials VALUES ('primary_ecu', 'primary_hw', 1);", nullptr, nullptr) != SQLITE_OK) {
335  FAIL();
336  }
337 
338  // run migration
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";
342  }
343 
344  // check values
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";
348  }
349 
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);
353 
354  if (statement.step() != SQLITE_ROW) {
355  FAIL() << "ecu_serials contains only one element";
356  }
357 
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);
361 
362  if (statement.step() != SQLITE_DONE) {
363  FAIL() << "Too many rows";
364  }
365 }
366 
367 TEST(sqlstorage, DbMigration19to20) {
368  // it must use raw sql primitives because the SQLStorage object does automatic
369  // migration + the api changes with time
370  auto tdb = makeDbWithVersion(DbVersion(19));
371  SQLite3Guard db(tdb.db_path.c_str());
372 
373  if (db.exec("INSERT INTO ecu_serials(serial,hardware_id,is_primary) VALUES ('primary_ecu', 'primary_hw', 1);",
374  nullptr, nullptr) != SQLITE_OK) {
375  FAIL();
376  }
377 
378  if (db.exec("INSERT INTO installed_versions VALUES ('primary_ecu', 'shav1', 'v1', 'sha256:shav1', 2, 'cor1', 0, 0);",
379  nullptr, nullptr) != SQLITE_OK) {
380  FAIL();
381  }
382  if (db.exec("INSERT INTO installed_versions VALUES ('primary_ecu', 'shav2', 'v2', 'sha256:shav2', 3, 'cor2', 1, 0);",
383  nullptr, nullptr) != SQLITE_OK) {
384  FAIL();
385  }
386 
387  // run migration
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";
391  }
392 
393  // check values
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";
399  }
400 
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);
410 
411  if (statement.step() != SQLITE_ROW) {
412  FAIL() << "installed_versions contains only one element";
413  }
414 
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);
424 
425  if (statement.step() != SQLITE_DONE) {
426  FAIL() << "Too many rows";
427  }
428 }
429 
430 /**
431  * Check that old metadata is still valid
432  */
433 TEST(sqlstorage, migrate_root_works) {
434  TemporaryDirectory temp_dir;
435  StorageConfig config;
436  config.path = temp_dir.Path();
437 
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));
440  SQLStorage storage(config, false);
441 
442  EXPECT_TRUE(storage.dbMigrate());
443  EXPECT_TRUE(dbSchemaCheck(storage));
444 
445  // Director
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));
450 
451  std::string raw_director_targets;
452  storage.loadNonRoot(&raw_director_targets, Uptane::RepositoryType::Director(), Uptane::Role::Targets());
453 
454  EXPECT_TRUE(director.verifyTargets(raw_director_targets));
455 
456  // Images
457  std::string raw_images_root;
458  storage.loadRoot(&raw_images_root, Uptane::RepositoryType::Image(), Uptane::Version());
459  Uptane::ImagesRepository imagesrepository;
460  EXPECT_TRUE(imagesrepository.initRoot(raw_images_root));
461 
462  // Check that the roots are different and haven't been swapped
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"));
468 }
469 
470 /* Migrate from the legacy filesystem storage. */
471 TEST(sqlstorage, migrate_from_fs) {
472  TemporaryDirectory temp_dir;
473  StorageConfig config;
474  config.path = temp_dir.Path() / "config";
475 
476  // copy a snapshot from a real provisioned device using fs storage
477  // (anonymized data)
478  Utils::copyDir(test_data_dir / "fs_snapshot", config.path);
479  ASSERT_GE(chmod(config.path.c_str(), S_IRWXU), 0);
480 
481  {
482  auto storage = INvStorage::newStorage(config);
483 
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));
488 
489  EcuSerials serials;
490  EXPECT_TRUE(storage->loadEcuSerials(&serials));
491  EXPECT_EQ(serials.size(), 3);
492 
493  std::vector<MisconfiguredEcu> misconfigured;
494  EXPECT_TRUE(storage->loadMisconfiguredEcus(&misconfigured));
495  EXPECT_EQ(misconfigured.size(), 1);
496 
497  EXPECT_TRUE(storage->loadEcuRegistered());
498 
499  boost::optional<Uptane::Target> installed;
500  storage->loadPrimaryInstalledVersions(&installed, nullptr);
501  EXPECT_TRUE(!!installed);
502  }
503 
504  // note: installation result is not migrated anymore
505 
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)) {
510  continue;
511  }
512  FAIL() << p << " should not exist anymore";
513  }
514 }
515 
516 #ifndef __NO_MAIN__
517 int main(int argc, char** argv) {
518  ::testing::InitGoogleTest(&argc, argv);
519  logger_init();
520  logger_set_threshold(boost::log::trivial::trace);
521  if (argc != 2) {
522  std::cout << "Please pass the directory containing sql migration scripts as the first argument\n";
523  return 1;
524  }
525 
526  test_data_dir = argv[1];
527 
528  if (!boost::filesystem::is_directory(test_data_dir)) {
529  std::cout << test_data_dir << " is not a directory\n";
530  return 1;
531  }
532 
533  return RUN_ALL_TESTS();
534 }
535 #endif
Uptane::DirectorRepository
Definition: directorrepository.h:13
Uptane::Version
Metadata version numbers.
Definition: tuf.h:116
StorageException
Definition: storage_exception.h:4
StorageConfig
Definition: storage_config.h:15
TempSQLDb
Definition: sqlstorage_test.cc:129
TemporaryDirectory
Definition: utils.h:82
result
Results of libaktualizr API calls.
Definition: results.h:13
SQLite3Guard
Definition: sql_utils.h:125
SQLStorage
Definition: sqlstorage.h:18
Uptane::ImagesRepository
Definition: imagesrepository.h:13